/*
** Main.c
*/

#define EXTERN
#include "dis6502.h"
#include "atari.h"
#include "version.h"
#include <ver.h>

/*
 * read an absolute sector (128 or 256 bytes).
 */
static void AtariReadAbsoluteSector(const char *szDisk, unsigned wSector, unsigned char *szSector, int *nSize)
{
ImgInfo Info;
ImgRWPacket Sector;

     ImageGetInfo(szDisk, &Info);
     if (Info.nResult >= 0)
          {
          Sector.lpszFileName = szDisk;
          Sector.wSectorNumber = wSector;
          Sector.wSectorSize = Info.wDensity;
          ImageReadSector(&Sector);
          if (Sector.nResult >= 0)
               {
               memcpy(szSector, Sector.cSectorData, Sector.wSectorSize);
               if (wSector <= 3)
                    *nSize = 128;
               else *nSize = Sector.wSectorSize;
               }
          }
}

/*
 * write an absolute sector (128 or 256 bytes).
 */
static void AtariWriteAbsoluteSector(const char *szDisk, unsigned wSector, unsigned char *szSector)
{
ImgInfo Info;
ImgRWPacket Sector;

     ImageGetInfo(szDisk, &Info);
     if (Info.nResult >= 0)
          {
          Sector.lpszFileName = szDisk;
          Sector.wSectorNumber = wSector;
          Sector.wSectorSize = Info.wDensity;
          memcpy(Sector.cSectorData, szSector, Sector.wSectorSize);
          ImageWriteSector(&Sector);
          }
}

/*
** Version string table for modules compatibility
*/
typedef struct {
    char    szModuleName[16];
    char    szModuleVersion[256];
    char    szModuleDesc[256];
} MODULE_VERSION;

MODULE_VERSION  ModuleVersionTable[] = {
    { "DIS6502.EXE" , "", "" },
    { "DUMPCTL.DLL" , "", "" },
    { "DISCTL.DLL"  , "", "" },
    { "SPRITCTL.DLL", "", "" },
    { ""            , "", "" }
};

/*
** get version info for one filename
*/
static void GetModuleVersion(char *szFileName, WORD index)
{
BYTE     VerData[512];
DWORD    handle;
DWORD    dwSize;
UINT     wSize;
LPBYTE   lpBuffer;
char     szName[256];
OFSTRUCT ofstruct;

     /*
     ** get info structure
     */
     strcpy(ModuleVersionTable[index].szModuleVersion, "-");
     strcpy(ModuleVersionTable[index].szModuleDesc, "-");
     if (OpenFile(szFileName, &ofstruct, OF_EXIST) != HFILE_ERROR)
          {        
          dwSize = GetFileVersionInfoSize(szFileName, &handle);
          if (dwSize != NULL)
               {
               GetFileVersionInfo(szFileName, handle, dwSize, VerData);

               /*
               ** get module version
               */
               VerQueryValue(VerData, "\\VarFileInfo\\Translation", &lpBuffer, &wSize);
               if (wSize != NULL)
                    {
                    wsprintf(szName, "\\StringFileInfo\\%04X%04X\\ProductVersion", *((WORD *)lpBuffer), *((WORD *)(lpBuffer + 2)));
                    VerQueryValue(VerData, szName, &lpBuffer, &wSize);
                    strcpy(ModuleVersionTable[index].szModuleVersion, lpBuffer);
                    }
               VerQueryValue(VerData, "\\VarFileInfo\\Translation", &lpBuffer, &wSize);
               if (wSize != NULL)
                    {
                    wsprintf(szName, "\\StringFileInfo\\%04X%04X\\FileDescription", *((WORD *)lpBuffer), *((WORD *)(lpBuffer + 2)));
                    VerQueryValue(VerData, szName, &lpBuffer, &wSize);
                    strcpy(ModuleVersionTable[index].szModuleDesc, lpBuffer);
                    }
               }
          }
}

/*
** get version info for one filename
*/
static void GetOneModuleVersion(WORD wIndex)
{
char szFullPathName[256];
char *szSlash;

     /*
     ** build full filename
     */
     GetModuleFileName(hInst, szFullPathName, sizeof(szFullPathName));
     if (szSlash = strrchr(szFullPathName, '\\'))
          strcpy(szSlash + 1, ModuleVersionTable[wIndex].szModuleName);
     GetModuleVersion(szFullPathName, wIndex);
}

/*
** get version info for one filename
*/
static void GetAllModuleVersion(HWND hList)
{
WORD wFile;
char szLine[256];
WORD wNbDll;

     wNbDll = sizeof(ModuleVersionTable) / sizeof(MODULE_VERSION);
     strcpy(ModuleVersionTable[wNbDll - 1].szModuleName, szComputerName);
     strcat(ModuleVersionTable[wNbDll - 1].szModuleName, "SYS.DLL");
     for (wFile = 0; wFile < wNbDll; wFile++)
          {
          GetOneModuleVersion(wFile);
          wsprintf(szLine, "%s\t%s\t%s", ModuleVersionTable[wFile].szModuleName,
                                         ModuleVersionTable[wFile].szModuleVersion,
                                         ModuleVersionTable[wFile].szModuleDesc);
          SendMessage(hList, LB_ADDSTRING, 0, (LPARAM) (LPCSTR) szLine);
          }
}

BOOL __export CALLBACK AboutProc(HWND hDlg, UINT message, WORD wParam, LONG lParam)
{
     switch (message)
          {
          case WM_INITDIALOG:
               GetAllModuleVersion(GetDlgItem(hDlg, IDC_LIST_VERSION));
               return TRUE;

          case WM_COMMAND:
               if (wParam == IDOK || wParam == IDCANCEL)
                    EndDialog(hDlg, TRUE);
               return TRUE;
          }
     return FALSE;
}

void RefreshToolsMenu(void)
{
     if (hMainWnd)
          {
          EnableMenuItem(GetMenu(hMainWnd), ID_TOOLS_MERGESEGMENTS, MF_BYCOMMAND | (wDumpSegment != NO_DUMP ? MF_ENABLED : MF_GRAYED));
          EnableMenuItem(GetMenu(hMainWnd), ID_TOOLS_WRITEBOOTDISK, MF_BYCOMMAND | (wDumpSegment != NO_DUMP ? MF_ENABLED : MF_GRAYED));
          }
}

void MainSetName(void)
{
     /*
     ** find name in the path.
     */
     if (szBinName = strrchr(szBinPath, '\\'))
          szBinName++;
     else {
          if (szBinName = strrchr(szBinPath, ':'))
               szBinName++;
          else szBinName = szBinPath;
          }

     /*
     ** redraw main window
     */
     InvalidateRect(hMainWnd, NULL, FALSE);
}

static BOOL MainAskForReset(HWND hWnd)
{
char szAsk[256];

     if (SegmentGetCount() == 0)
          return TRUE;
     if (LoadString(hInst, IDS_ASK_REPLACE, szAsk, sizeof(szAsk)) == 0)
          strcpy(szAsk, "Do you want to replace existing segments ?\n\nIf you answer No, these new segments will be added to the existing ones.");
     if (MessageBox(hWnd, szAsk, szTitleApp, MB_YESNO | MB_ICONQUESTION) == IDYES)
          return TRUE;
     return FALSE;
}

void MainClean(BOOL bAskForReset)
{
BOOL bReset = TRUE;

     /*
     ** set loaded filename to empty.
     */
     szBinName = NULL;

     /*
     ** reset segment window
     */
     if (bAskForReset && hMainWnd)
          if (MainAskForReset(hMainWnd) == FALSE)
               bReset = FALSE;
     if (bReset)
          SegmentReset(hMainWnd);

     /*
     ** reset dump window
     */
     DumpReset(hMainWnd);

     /*
     ** reset dis window
     */
     DisReset(hMainWnd);

     /*
     ** free code label
     */
     LabelFreeCode();

     /*
     ** redraw main window
     */
     if (hMainWnd)
          InvalidateRect(hMainWnd, NULL, FALSE);
     RefreshToolsMenu();
}

static long MainCreate(HWND hWnd)
{
     /*
     ** load Atari font.
     */
     if (bFontOK)
          hComputerFont = CreateFont(-FONT_HEIGHT, 0, 0, 0, 0, 0, 0, 0, OEM_CHARSET, 0, 0, 0, FIXED_PITCH, szComputerName);
     if (hComputerFont == 0)
          hComputerFont = CreateFont(-FONT_HEIGHT, 0, 0, 0, 0, 0, 0, 0, OEM_CHARSET, 0, 0, 0, FIXED_PITCH, "Terminal");
     EnableMenuItem(GetMenu(hMainWnd), ID_TOOLS_MERGESEGMENTS, FALSE);
     EnableMenuItem(GetMenu(hMainWnd), ID_TOOLS_WRITEBOOTDISK, FALSE);
     return NULL;
}

static long MainPaint(HWND hWnd)
{
PAINTSTRUCT ps;
HDC hDC;
HFONT hOldFont;
char szBuf[120];
RECT rcMain;
RECT rcChild;
RECT rc;
COLORREF rgbOldBk;
WORD wSize;

     /*
     ** set Atari font
     */
     hDC = BeginPaint(hWnd, &ps);
     hOldFont = SelectObject(hDC, hComputerFont);

     /*
     ** print name of binary file.
     */
     rgbOldBk = SetBkColor(hDC, RGB(255, 255, 0));
     if (szBinName == NULL)
          strcpy(szBuf, szNoSegmentTitle);
     else wsprintf(szBuf, szSegmentTitle, szBinName);
     GetClientRect(hWnd, &rcMain);
     ClientToScreen(hWnd, (POINT *) &rcMain);
     GetWindowRect(hSegmentList, &rcChild);
     rc.right = rcChild.right - rcChild.left;
     rc.left = 0;
     rc.bottom = rcChild.top - rcMain.top;
     rc.top = 0;
     ExtTextOut(hDC, 1, 1, ETO_CLIPPED | ETO_OPAQUE, &rc, szBuf, strlen(szBuf), NULL);

     /*
     ** print dis title.
     */
     rgbOldBk = SetBkColor(hDC, RGB(0, 255, 0));
     rc.left = rc.right;
     rc.right = rcMain.right;
     ExtTextOut(hDC, rc.left + 1, rc.top + 1, ETO_CLIPPED | ETO_OPAQUE, &rc, szDisassembly, strlen(szDisassembly), NULL);
     rc.right = rc.left;
     rc.left = 0;

     /*
     ** print dump title.
     */
     rgbOldBk = SetBkColor(hDC, RGB(0, 255, 255));
     if (wDumpSegment == NO_DUMP)
          strcpy(szBuf, szNoDumpTitle);
     else {
          wSize = Segment[wDumpSegment].wEnd - Segment[wDumpSegment].wBegin + 1;
          wsprintf(szBuf, szDumpTitle, Segment[wDumpSegment].wBegin, Segment[wDumpSegment].wEnd, wSize);
          }
     rc.top = rc.bottom + rcChild.bottom - rcChild.top;
     rc.bottom += rc.top;
     ExtTextOut(hDC, 1, rc.top + 1, ETO_CLIPPED | ETO_OPAQUE, &rc, szBuf, strlen(szBuf), NULL);

     /*
     ** restore context.
     */
     SetBkColor(hDC, rgbOldBk);
     SelectObject(hDC, hOldFont);
     EndPaint(hWnd, &ps);
     return NULL;
}

static long MainSegment(HWND hWnd, LPARAM lParam)
{
     switch (HIWORD(lParam))
          {

          /*
          ** the user has selected a segment.
          */
          case LBN_SELCHANGE:
               SegmentSelected();
               break;
          }
     return NULL;
}

static long MainDump(HWND hWnd, LPARAM lParam)
{
     switch (HIWORD(lParam))
          {
          /*
          ** user has changed the selection
          */
          case DUMP_SELECTION_CHANGED:
               DumpSelChanged(hWnd);
               break;

          /*
          ** user has right clicked in the window
          */
          case DUMP_RBUTTONDOWN:
               DumpDrawMenu(hWnd);
               break;
          }
     return NULL;
}

static long MainOpenFile(HWND hWnd)
{
int fd;

     /*
     ** open filename.
     */
     if ((fd = open(szBinPath, _O_RDONLY | _O_BINARY)) < 0)
          Error(hWnd, IDS_ERR_OPENING_FILE, "Error while opening file %s", szBinPath);
     else {

          /*
          ** read file and fill all listboxes.
          */
          MainSetName();
          if ((*CompuReadFile)(hSegmentList, fd, Segment, SegmentGetFirstFree()) == FALSE)
               Error(hWnd, IDS_ERR_READING_FILE,
                     "Error reading file %s.\nEither the file is corrupted or it is not an %s binary file\nor the file contains more than 256 segments !",
                     szBinPath, szComputerName);
          else {
               SendMessage(hSegmentList, LB_SETCURSEL, 0, 0L);
               SegmentSelected();
               SetFocus(hSegmentList);
               DisProcess(hInst, hWnd);
               }
          close(fd);
          }

     /*
     ** everything OK.
     */
     return NULL;
}

static long MainChooseFile(HWND hWnd)
{
     /*
     ** ask the user for a filename.
     */
     if (GetFileName(hWnd, szBinPath,
                     OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST,
                     IDS_OPEN_BINARY,
                     "COM files\0*.COM\0BIN files\0*.BIN\0EXE files\0*.EXE\0All files\0*.*\0\0"))
          {

          /*
          ** clean and free all stuff for the previous binary file.
          */
          MainClean(TRUE);
          MainOpenFile(hWnd);
          }
     return NULL;
}

int AtariRead(AtariFile *Info, char *szBuf, int iLen)
{
WORD wSectorLng;
int iTotal;

     /*
     ** read first sector if we are at the begining.
     */
     if (iDiskIndex == -1)
          {
          if (AtariReadFirstSector(szAtrPath, Info) != ATARI_ERR_OK)
               return 0;
          else iDiskIndex = 0;
          }

     /*
     ** copy bytes from sector to user buffer.
     */
     iTotal = 0;
     wSectorLng = Info->wSectorLng - iDiskIndex;
     while (wSectorLng < (WORD) iLen)
          {
          if (wSectorLng)
               {
               memcpy(szBuf, &(Info->szSector[iDiskIndex]), wSectorLng);
               szBuf += wSectorLng;
               iLen -= wSectorLng;
               iTotal += wSectorLng;
               }
          iDiskIndex = 0;
          if (AtariReadNextSector(szAtrPath, Info) != ATARI_ERR_OK)
               {
               Info->wSectorLng = 0;
               return iTotal;
               }
          wSectorLng = Info->wSectorLng - iDiskIndex;
          }
     memcpy(szBuf, &(Info->szSector[iDiskIndex]), iLen);
     iDiskIndex += iLen;
     iTotal += iLen;
     return iTotal;
}

static long AtariFileSize(AtariFile *Info)
{
AtariError iRet;
long lSize;

     /*
      * boucle de lecture de tous les secteurs du fichier.
      */
     lSize = 0;
     iRet = AtariReadFirstSector(szAtrPath, Info);
     while ((iRet == ATARI_ERR_OK) && (Info->wSectorLng > 0))
          {
          lSize += (long) (int) Info->wSectorLng;
          iRet = AtariReadNextSector(szAtrPath, Info);
          }
     return lSize;
}

static BOOL DiskReadFile(AtariFile *Info)
{
short iMagic;
DWORD dwFileLength;
WORD wBegin, wEnd;
WORD wSize;
int nSeg;
char szBuf[80];
DWORD dwIndex;

     /*
     ** get file size
     */
     dwFileLength = (DWORD) AtariFileSize(Info);

     /*
     ** Check that the file begins with 2 0xFF.
     */
     iDiskIndex = -1;
     if (AtariRead(Info, (char *) &iMagic, sizeof(iMagic)) != sizeof(iMagic))
          return FALSE;
     if (iMagic != -1)
          return FALSE;
     dwFileLength -= sizeof(iMagic);

     /*
     ** read all segments
     */
     nSeg = SegmentGetFirstFree();
     while (dwFileLength)
          {

          /*
          ** read segment start and end address.
          */
          if (AtariRead(Info, (char *) &wBegin, sizeof(wBegin)) != sizeof(wBegin))
               return TRUE;
          if (nSeg && (wBegin == 0xFFFF))
               {
               if (AtariRead(Info, (char *) &wBegin, sizeof(wBegin)) != sizeof(wBegin))
                    return FALSE;
               dwFileLength -= 2;
               }
          if (AtariRead(Info, (char *) &wEnd, sizeof(wEnd)) != sizeof(wEnd))
               return FALSE;
          if (wEnd < wBegin)
               return FALSE;
          dwFileLength -= sizeof(wBegin) + sizeof(wEnd);

          /*
          ** check data size.
          */
          wSize = wEnd - wBegin + 1;
          if ((DWORD) wSize > dwFileLength)
               return FALSE;

          /*
          ** read segment data.
          */
          Segment[nSeg].lpDump = MemoryAlloc(wSize);
          if (Segment[nSeg].lpDump == NULL)
               return FALSE;
          Segment[nSeg].lpType = MemoryAlloc(wSize);
          if (Segment[nSeg].lpType == NULL)
               {
               MemoryFree(Segment[nSeg].lpDump);
               Segment[nSeg].lpDump = NULL;
               return FALSE;
               }
          Segment[nSeg].wBegin = wBegin;
          Segment[nSeg].wEnd = wEnd;
          Segment[nSeg].bBinary = TRUE;
          if ((WORD) AtariRead(Info, Segment[nSeg].lpDump, wSize) != wSize)
               return FALSE;
          dwFileLength -= (DWORD) wSize;

          /*
          ** add this segment to the segment list.
          */
          wsprintf(szBuf, "%04X-%04X:%04X (%u)", wBegin, wEnd, wSize, wSize);
          dwIndex = SendMessage(hSegmentList, LB_ADDSTRING, 0, (LPARAM) (LPCSTR) szBuf);
          if (dwIndex == LB_ERR)
               return FALSE;

          /*
          ** next segment.
          */
          nSeg++;
          if (nSeg >= MAX_SEGMENTS)
               return FALSE;
          }
     return TRUE;
}

static void DiskOpenFile(HWND hWnd, AtariFile *Info)
{
     /*
     ** clean and free all stuff for the previous binary file.
     */
     MainClean(TRUE);

     /*
     ** read file and fill all listboxes.
     */
     MainSetName();

     if (DiskReadFile(Info) == FALSE)
          Error(hWnd, IDS_ERR_READING_FILE,
                "Error reading file %s.\nEither the file is corrupted or it is not an %s binary file\nor the file contains more than 256 segments !",
                szBinPath, szComputerName);
     else {
          SendMessage(hSegmentList, LB_SETCURSEL, 0, 0L);
          SegmentSelected();
          SetFocus(hSegmentList);
          DisProcess(hInst, hWnd);
          }
}

BOOL __export CALLBACK DiskImageProc(HWND hDlg, UINT message, WORD wParam, LONG lParam)
{
AtariFile Info;
AtariError Err;
char szBuf[40];
char *p;
WORD wLen;
DWORD dwIndex;
BOOL bOK;

     switch (message)
          {
          case WM_INITDIALOG:
               SendDlgItemMessage(hDlg, IDC_LISTFILE, WM_SETFONT, hComputerFont, (LPARAM) MAKELONG(0, FALSE));
               SendDlgItemMessage(hDlg, IDC_LISTHEADER, WM_SETFONT, hComputerFont, (LPARAM) MAKELONG(0, FALSE));
               SendDlgItemMessage(hDlg, IDC_BINARYFILE, WM_SETTEXT, 0, (LPARAM) (LPCSTR) "");
               SendDlgItemMessage(hDlg, IDC_DISKIMAGE, WM_SETTEXT, 0, (LPARAM) (LPCSTR) szAtrPath);
               Err = AtariFindFirst(szAtrPath, &Info);
               while (Err == ATARI_ERR_OK)
                    {
                    if (Info.wAttrib & ATARI_ATTR_LOCKED)
                         strcpy(szBuf, " * ");
                    else strcpy(szBuf, "   ");
                    strcat(szBuf, Info.szName);
                    if (p = strchr(szBuf, '.'))
                         *p = 0;
                    for (wLen = strlen(szBuf); wLen < 11; wLen++)
                         szBuf[wLen] = ' ';
                    szBuf[wLen] = 0;
                    if (p = strchr(Info.szName, '.'))
                         {
                         strcat(szBuf, p + 1);
                         wLen = strlen(p + 1);
                         }
                    else wLen = 0;
                    switch (wLen)
                         {
                         case 0:
                              strcat(szBuf, "   ");
                              break;
                         case 1:
                              strcat(szBuf, "  ");
                              break;
                         case 2:
                              strcat(szBuf, " ");
                              break;
                         }
                    wsprintf(szBuf + strlen(szBuf), " %4u %4u", Info.wSize, Info.wStart);
                    dwIndex = SendDlgItemMessage(hDlg, IDC_LISTFILE, LB_ADDSTRING, 0, (LPARAM) (LPCSTR) szBuf);
                    SendDlgItemMessage(hDlg, IDC_LISTFILE, LB_SETITEMDATA, (WORD) dwIndex, (LPARAM) Info.lIndex);
                    Err = AtariFindNext(szAtrPath, &Info);
                    }
               EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
               return TRUE;

          case WM_COMMAND:
               switch (wParam)
                    {
                    case IDCANCEL:
                         EndDialog(hDlg, FALSE);
                         return TRUE;

                    case IDOK:
                         dwIndex = SendDlgItemMessage(hDlg, IDC_LISTFILE, LB_GETCURSEL, 0, 0);
                         if (dwIndex != LB_ERR)
                              {
                              dwIndex = SendDlgItemMessage(hDlg, IDC_LISTFILE, LB_GETITEMDATA, (WORD) dwIndex, 0L);
                              if (AtariGetFileFromIndex(szAtrPath, &Info, dwIndex) == ATARI_ERR_OK)
                                   {
                                   strcpy(szBinPath, Info.szName);
                                   DiskOpenFile(hDlg, &Info);
                                   EndDialog(hDlg, TRUE);
                                   }
                              }
                         return TRUE;

                    case IDC_LISTFILE:
                         switch (HIWORD(lParam))
                              {
                    
                              /*
                              ** the user has selected a file.
                              */
                              case LBN_SELCHANGE:
                                   bOK = FALSE;
                                   dwIndex = SendDlgItemMessage(hDlg, IDC_LISTFILE, LB_GETCURSEL, 0, 0);
                                   if (dwIndex != LB_ERR)
                                        {
                                        dwIndex = SendDlgItemMessage(hDlg, IDC_LISTFILE, LB_GETITEMDATA, (WORD) dwIndex, 0L);
                                        if (AtariGetFileFromIndex(szAtrPath, &Info, dwIndex) != ATARI_ERR_OK)
                                             Error(hDlg, IDS_ERR_ATARI_FILE, "File not found in disk image");
                                        else bOK = TRUE;
                                        }
                                   if (bOK)
                                        SendDlgItemMessage(hDlg, IDC_BINARYFILE, WM_SETTEXT, 0, (LPARAM) (LPCSTR) Info.szName);
                                   else SendDlgItemMessage(hDlg, IDC_BINARYFILE, WM_SETTEXT, 0, (LPARAM) (LPCSTR) "");
                                   EnableWindow(GetDlgItem(hDlg, IDOK), bOK);
                                   break;
                    
                              /*
                              ** the user has double-clicked on a file.
                              */
                              case LBN_DBLCLK:
                                   dwIndex = SendDlgItemMessage(hDlg, IDC_LISTFILE, LB_GETCURSEL, 0, 0);
                                   if ((dwIndex != LB_ERR) && (IsWindowEnabled(GetDlgItem(hDlg, IDOK))))
                                        PostMessage(hDlg, WM_COMMAND, IDOK, NULL);
                                   break;
                              }
                         return TRUE;
                    }
               break;

          case WM_CTLCOLOR:

               /*
               ** change background color to gray.
               */
               if (HIWORD(lParam) == CTLCOLOR_STATIC || HIWORD(lParam) == CTLCOLOR_BTN || HIWORD(lParam) == CTLCOLOR_DLG)
                    {
                    SetBkColor((HDC) wParam, RGB(192, 192, 192));
                    return (BOOL) GetStockObject(LTGRAY_BRUSH);
                    }
               return FALSE;
          }
     return FALSE;
}

static long MainDiskOpenFile(HWND hWnd)
{
AtariFile Info;

     switch (AtariFindFirst(szAtrPath, &Info))
          {
          case ATARI_ERR_OK:
               DialogBox(hInst, "ATRFILEBOX", hWnd, DiskImageProc);
               break;

          case ATARI_ERR_DISK_NOT_FOUND:
               Error(hWnd, IDS_ERR_OPENING_FILE, "Error while opening file %s", szAtrPath);
               break;

          case ATARI_ERR_NO_ENTRY_FOUND:
               Error(hWnd, IDS_ERR_NO_ATARI_FILE, "No file found in disk image %s", szAtrPath);
               break;

          default:
               Error(hWnd, IDS_ERR_READING_ATR,
                     "Error reading file %s.\nEither the disk image file is corrupted or it is not an Atari single/enhanced density disk image file\nor the file contains more than 256 segments !",
                     szAtrPath);
               break;
          }
     return NULL;
}

static long MainChooseAtrDisk(HWND hWnd)
{
     /*
     ** ask the user for a filename.
     */
     if (GetFileName(hWnd, szAtrPath,
                     OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST,
                     IDS_OPEN_DISK_IMAGE,
                     "ATR files\0*.ATR\0XFD files\0*.XFD\0All files\0*.*\0\0"))
          MainDiskOpenFile(hWnd);
     return NULL;
}

static void BootOpenFile(BYTE *szSector)
{
WORD wSectors;
WORD wNumSec;
WORD wSecSize;
WORD wBegin, wEnd;
WORD wSize;
BYTE *lpDump;
int nSeg;
ImgRWPacket Sector;

     if (nSeg = SegmentGetFirstFree() == -1)
          {
          Error(hMainWnd, IDS_ERR_NO_FREE_SEG, "No more free segments (maximum is 256) !");
          return;
          }
     strcpy(szBinPath, "BOOT");
     MainSetName();
     wSectors = (WORD) szSector[1];
     wBegin = szSector[2] + (szSector[3] * 256) + 6;
     wSize = (wSectors * 128) - 6;
     wEnd = wBegin + wSize - 1;
     Segment[nSeg].lpDump = MemoryAlloc(wSize);
     if (Segment[nSeg].lpDump == NULL)
          {
          Error(hMainWnd, IDS_ERR_NO_MEMORY, "Could not allocate memory !");
          return;
          }
     Segment[nSeg].lpType = MemoryAlloc(wSize);
     if (Segment[nSeg].lpType == NULL)
          {
          MemoryFree(Segment[nSeg].lpDump);
          Segment[nSeg].lpDump = NULL;
          Error(hMainWnd, IDS_ERR_NO_MEMORY, "Could not allocate memory !");
          return;
          }
     Segment[nSeg].wBegin = wBegin;
     Segment[nSeg].wEnd = wEnd;
     Segment[nSeg].bBinary = TRUE;
     lpDump = Segment[nSeg].lpDump;
     wSecSize = min(128 - 6, wSize);
     memcpy(lpDump, szSector + 6, wSecSize);
     wSize -= wSecSize;
     wSectors--;
     wNumSec = 2;
     while (wSize && wSectors)
          {
          lpDump += wSecSize;
          wSecSize = min(128, wSize);
          Sector.lpszFileName = szAtrPath;
          Sector.wSectorNumber = wNumSec++;
          Sector.wSectorSize = 128; /* I assume that boot sectors are 128 bytes long. */
          ImageReadSector(&Sector);
          if (Sector.nResult >= 0)
               {
               memcpy(lpDump, Sector.cSectorData, 128);
               wSize -= wSecSize;
               wSectors--;
               }
          else break;
          }

     /*
     ** add this segment to the segment list.
     */
     wSize = wEnd - wBegin + 1;
     wsprintf(szErr, "%04X-%04X:%04X (%u)", wBegin, wEnd, wSize, wSize);
     SendMessage(hSegmentList, LB_ADDSTRING, 0, (LPARAM) (LPCSTR) szErr);
     SendMessage(hSegmentList, LB_SETCURSEL, 0, 0L);
     SegmentSelected();
     SetFocus(hSegmentList);
     DisProcess(hInst, hMainWnd);
}

static long MainBootOpenFile(HWND hWnd)
{
ImgInfo Info;
ImgRWPacket Sector;

     ImageGetInfo(szAtrPath, &Info);
     if (ImageError(hWnd, Info.nResult) >= 0)
          {
          Sector.lpszFileName = szAtrPath;
          Sector.wSectorNumber = 1;
          Sector.wSectorSize = 128; /* I assume that boot sector is always 128 bytes long. */
          ImageReadSector(&Sector);
          if (ImageError(hWnd, Info.nResult) >= 0)
               {
               if (Sector.cSectorData[1] == 0)
                    Error(hWnd, IDS_ERR_NO_BOOT, "Disk image %s is not bootable", szAtrPath);
               else {
                    MainClean(TRUE);
                    BootOpenFile(Sector.cSectorData);
                    }
               }
          }
     return NULL;
}

static long MainChooseAtrBoot(HWND hWnd)
{
     /*
     ** ask the user for a filename.
     */
     if (GetFileName(hWnd, szAtrPath,
                     OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST,
                     IDS_OPEN_DISK_IMAGE,
                     "ATR files\0*.ATR\0XFD files\0*.XFD\0All files\0*.*\0\0"))
          MainBootOpenFile(hWnd);
     return NULL;
}

static void ExpandSectorWindow(HWND hDlg, int iSectorSize)
{
int iDiff;
RECT rcDump, rcDlg;
POINT pt;

     pt.x = pt.y = 0;
     ClientToScreen(hDlg, &pt);
     GetClientRect(GetDlgItem(hDlg, IDC_SECDUMP), &rcDump);
     iDiff = rcDump.bottom;
     GetWindowRect(hDlg, &rcDlg);
     GetWindowRect(GetDlgItem(hDlg, IDC_SECDUMP), &rcDump);
     if (iSectorSize == 256)
          {
          rcDlg.bottom += iDiff;
          rcDump.bottom += iDiff;
          }
     else {
          rcDlg.bottom -= (iDiff / 2);
          rcDump.bottom -= (iDiff / 2);
          }
     MoveWindow(hDlg, rcDlg.left, rcDlg.top, rcDlg.right - rcDlg.left, rcDlg.bottom - rcDlg.top, TRUE);
     MoveWindow(GetDlgItem(hDlg, IDC_SECDUMP), rcDump.left - pt.x, rcDump.top - pt.y, rcDump.right - rcDump.left, rcDump.bottom - rcDump.top, TRUE);
}

/*
 * read an absolute sector (128 or 256 bytes).
 */
static void ReadAbsoluteSector(ImgRWPacket *Sector, unsigned wSector, int *nSize)
{
     Sector->wSectorNumber = wSector;
     ImageReadSector(Sector);
     if (Sector->nResult >= 0)
          {
          if (wSector <= 3)
               *nSize = 128;
          else *nSize = Sector->wSectorSize;
          }
}

BOOL __export CALLBACK SectorsProc(HWND hDlg, UINT message, WORD wParam, LONG lParam)
{
static WORD wSector;
static wCurrentSize;
WORD wOldSector;
WORD wAddr, wTmp;
WORD wBegin, wEnd;
WORD wSize;
char szNewSec[80];
int iItem, iNbItem;
int nSeg, iSectorSize;
BOOL bSelected;
static ImgInfo Info;
static ImgRWPacket Sector;

     switch (message)
          {
          case WM_INITDIALOG:
               ImageGetInfo(szAtrPath, &Info);
               Sector.lpszFileName = szAtrPath;
               Sector.wSectorSize = Info.wDensity;
               SetDlgItemText(hDlg, IDC_ATRFILENAME, szAtrPath);
               wCurrentSize = 128;
               iSectorSize = 128;
               wSector = 1;
               ReadAbsoluteSector(&Sector, wSector, &iSectorSize);
               if (iSectorSize != (int) wCurrentSize)
                    {
                    ExpandSectorWindow(hDlg, iSectorSize);
                    wCurrentSize = iSectorSize;
                    SendDlgItemMessage(hDlg, IDC_SECDUMP, WM_DUMP_SET_BUFFER, iSectorSize, (LPARAM) (LPCSTR) Sector.cSectorData);
                    }
               SetDlgItemInt(hDlg, IDC_SECTORNUMBER, wSector, FALSE);
               SendDlgItemMessage(hDlg, IDC_SECDUMP, WM_SETFONT, hComputerFont, (LPARAM) MAKELONG(0, FALSE));
               SendDlgItemMessage(hDlg, IDC_SECDUMP, WM_DUMP_SET_BUFFER, iSectorSize, (LPARAM) (LPCSTR) Sector.cSectorData);
               SendDlgItemMessage(hDlg, IDC_SECDUMP, WM_DUMP_SET_BEGIN_SELECTION, DUMP_NO_SELECTION, FALSE);
               SendDlgItemMessage(hDlg, IDC_SECDUMP, WM_DUMP_SET_END_SELECTION, DUMP_NO_SELECTION, FALSE);
               SetScrollRange(GetDlgItem(hDlg, IDC_SECTORSCROLL), SB_CTL, 1, Info.wSectors, FALSE);
               SetScrollPos(GetDlgItem(hDlg, IDC_SECTORSCROLL), SB_CTL, wSector, TRUE);
               SendDlgItemMessage(hDlg, IDC_LISTTITLE, WM_SETFONT, hComputerFont, (LPARAM) MAKELONG(0, FALSE));
               SendDlgItemMessage(hDlg, IDC_SECTORLIST, WM_SETFONT, hComputerFont, (LPARAM) MAKELONG(0, FALSE));
               SendDlgItemMessage(hDlg, IDC_ADDRSECTOR, EM_LIMITTEXT, 4, 0);
               EnableWindow(GetDlgItem(hDlg, IDC_REMOVESECTOR), FALSE);
               EnableWindow(GetDlgItem(hDlg, IDC_ADDSECTOR), FALSE);
               EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
               return TRUE;

          case WM_COMMAND:
               switch (wParam)
                    {
                    case IDCANCEL:
                         EndDialog(hDlg, FALSE);
                         return TRUE;

                    case IDOK:
                         MainClean(TRUE);
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_SECTORLIST, LB_GETCOUNT, 0, 0);
                         nSeg = SegmentGetFirstFree();
                         for (iItem = 0; iItem < iNbItem; iItem++)
                              {
                              if (nSeg < 0)
                                   {
                                   Error(hMainWnd, IDS_ERR_NO_FREE_SEG, "No more free segments (maximum is 256) !");
                                   break;
                                   }
                              SendDlgItemMessage(hDlg, IDC_SECTORLIST, LB_GETTEXT, iItem, (LPARAM) (LPCSTR) szErr);
                              sscanf(szErr, "%4d %04X %02X    %02X", &wSector, &wAddr, &wBegin, &wSize);
                              wEnd = wBegin + wSize - 1;
                              Segment[nSeg].lpDump = MemoryAlloc(wSize);
                              if (Segment[nSeg].lpDump == NULL)
                                   {
                                   Error(hDlg, IDS_ERR_NO_MEMORY, "Could not allocate memory !");
                                   break;
                                   }
                              Segment[nSeg].lpType = MemoryAlloc(wSize);
                              if (Segment[nSeg].lpType == NULL)
                                   {
                                   MemoryFree(Segment[nSeg].lpDump);
                                   Segment[nSeg].lpDump = NULL;
                                   Error(hDlg, IDS_ERR_NO_MEMORY, "Could not allocate memory !");
                                   break;
                                   }
                              Segment[nSeg].wBegin = wAddr;
                              Segment[nSeg].wEnd = wAddr + wSize - 1;
                              ReadAbsoluteSector(&Sector, wSector, &iSectorSize);
                              if (iSectorSize != (int) wCurrentSize)
                                   {
                                   ExpandSectorWindow(hDlg, iSectorSize);
                                   wCurrentSize = iSectorSize;
                                   SendDlgItemMessage(hDlg, IDC_SECDUMP, WM_DUMP_SET_BUFFER, iSectorSize, (LPARAM) (LPCSTR) Sector.cSectorData);
                                   }
                              memcpy(Segment[nSeg].lpDump, Sector.cSectorData + wBegin, wSize);

                              /*
                              ** add this segment to the segment list.
                              */
                              wsprintf(szErr, "%04X-%04X:%04X (%u)", wAddr, wAddr + wSize - 1, wSize, wSize);
                              SendMessage(hSegmentList, LB_ADDSTRING, 0, (LPARAM) (LPCSTR) szErr);
                              nSeg++;
                              if (nSeg >= MAX_SEGMENTS)
                                   nSeg = -1;
                              }
                         SendMessage(hSegmentList, LB_SETCURSEL, 0, 0L);
                         SegmentSelected();
                         DisProcess(hInst, hDlg);
                         EndDialog(hDlg, TRUE);
                         return TRUE;

                    case IDC_ADDRSECTOR:
                         if (HIWORD(lParam) == EN_CHANGE)
                              EnableWindow(GetDlgItem(hDlg, IDC_ADDSECTOR), (BOOL) SendDlgItemMessage(hDlg, IDC_ADDRSECTOR, WM_GETTEXTLENGTH, 0, 0));
                         return TRUE;

                    case IDC_ADDSECTOR:
                         GetDlgItemText(hDlg, IDC_ADDRSECTOR, szErr, 5);
                         sscanf(szErr, "%X", &wAddr);
                         wBegin = (WORD) SendDlgItemMessage(hDlg, IDC_SECDUMP, WM_DUMP_GET_BEGIN_SELECTION, 0, 0);
                         if (wBegin == DUMP_NO_SELECTION)
                              {
                              wBegin = 0;
                              wEnd = iSectorSize - 1;
                              }
                         else {
                              wEnd = (WORD) SendDlgItemMessage(hDlg, IDC_SECDUMP, WM_DUMP_GET_END_SELECTION, 0, 0);
                              if (wBegin > wEnd)
                                   {
                                   wTmp = wBegin;
                                   wBegin = wEnd;
                                   wEnd = wTmp;
                                   } 
                              }
                         wsprintf(szNewSec, "%4d %04X %02X    %02X", wSector, wAddr, wBegin, wEnd - wBegin + 1);
                         SendDlgItemMessage(hDlg, IDC_SECTORLIST, LB_ADDSTRING, 0, (LPARAM) (LPCSTR) szNewSec);
                         EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);
                         wAddr += wEnd - wBegin + 1;
                         wsprintf(szErr, "%04X", wAddr);
                         SetDlgItemText(hDlg, IDC_ADDRSECTOR, szErr);
                         if (wSector != Info.wSectors)
                              SendMessage(hDlg, WM_HSCROLL, SB_LINEDOWN, (LPARAM) MAKELONG(wSector, GetDlgItem(hDlg, IDC_SECTORSCROLL)));
                         return TRUE;

                    case IDC_REMOVESECTOR:
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_SECTORLIST, LB_GETCOUNT, 0, 0);
                         for (iItem = iNbItem - 1; iItem >= 0; iItem--)
                              if (SendDlgItemMessage(hDlg, IDC_SECTORLIST, LB_GETSEL, iItem, 0))
                                   SendDlgItemMessage(hDlg, IDC_SECTORLIST, LB_DELETESTRING, iItem, 0);
                         EnableWindow(GetDlgItem(hDlg, IDC_REMOVESECTOR), FALSE);
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_SECTORLIST, LB_GETCOUNT, 0, 0);
                         EnableWindow(GetDlgItem(hDlg, IDOK), (iNbItem > 0));
                         return TRUE;

                    case IDC_SECTORLIST:
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_SECTORLIST, LB_GETCOUNT, 0, 0);
                         EnableWindow(GetDlgItem(hDlg, IDOK), (iNbItem > 0));
                         if (HIWORD(lParam) == LBN_SELCHANGE)
                              {
                              bSelected = FALSE;
                              for (iItem = 0; iItem < iNbItem; iItem++)
                                   if (SendDlgItemMessage(hDlg, IDC_SECTORLIST, LB_GETSEL, iItem, 0))
                                        {
                                        bSelected = TRUE;
                                        break;
                                        }
                              EnableWindow(GetDlgItem(hDlg, IDC_REMOVESECTOR), bSelected);
                              }
                         return TRUE;
                    }
               break;

          case WM_HSCROLL:
               wOldSector = wSector;
               switch (wParam)
                    {
                    case SB_TOP:
                         if (wSector > 1)
                              wSector = 1;
                         break;

                    case SB_BOTTOM:
                         if (wSector != Info.wSectors)
                              wSector = Info.wSectors;
                         break;

                    case SB_PAGEUP:
                         if (wSector > 18)
                              wSector -= 18;
                         else wSector = 1;
                         break;

                    case SB_PAGEDOWN:
                         if (wSector < Info.wSectors - 18)
                              wSector += 18;
                         else wSector = Info.wSectors;
                         break;

                    case SB_LINEUP:
                         if (wSector > 1)
                              wSector--;
                         break;

                    case SB_LINEDOWN:
                         if (wSector < Info.wSectors)
                              wSector++;
                         break;

                    case SB_THUMBPOSITION:
                    case SB_THUMBTRACK:
                         wSector = LOWORD(lParam);
                         break;
                    }
               if (wOldSector != wSector)
                    {
                    ReadAbsoluteSector(&Sector, wSector, &iSectorSize);
                    if (iSectorSize != (int) wCurrentSize)
                         {
                         ExpandSectorWindow(hDlg, iSectorSize);
                         wCurrentSize = iSectorSize;
                         SendDlgItemMessage(hDlg, IDC_SECDUMP, WM_DUMP_SET_BUFFER, iSectorSize, (LPARAM) (LPCSTR) Sector.cSectorData);
                         }
                    SetDlgItemInt(hDlg, IDC_SECTORNUMBER, wSector, FALSE);
                    SetScrollPos(GetDlgItem(hDlg, IDC_SECTORSCROLL), SB_CTL, wSector, TRUE);
                    SendDlgItemMessage(hDlg, IDC_SECDUMP, WM_DUMP_SET_BEGIN_SELECTION, DUMP_NO_SELECTION, FALSE);
                    SendDlgItemMessage(hDlg, IDC_SECDUMP, WM_DUMP_SET_END_SELECTION, DUMP_NO_SELECTION, TRUE);
                    }
               return TRUE;

          case WM_CTLCOLOR:

               /*
               ** change background color to gray.
               */
               if (HIWORD(lParam) == CTLCOLOR_STATIC || HIWORD(lParam) == CTLCOLOR_BTN || HIWORD(lParam) == CTLCOLOR_DLG)
                    {
                    SetBkColor((HDC) wParam, RGB(192, 192, 192));
                    return (BOOL) GetStockObject(LTGRAY_BRUSH);
                    }
               return FALSE;
          }
     return FALSE;
}

static long MainSectorsOpenFile(HWND hWnd)
{
ImgInfo Info;

     ImageGetInfo(szAtrPath, &Info);
     if (ImageError(hWnd, Info.nResult) >= 0)
          DialogBox(hInst, "ATRSECTORSBOX", hWnd, SectorsProc);
     return NULL;
}

static long MainChooseAtrSectors(HWND hWnd)
{
     /*
     ** ask the user for a filename.
     */
     if (GetFileName(hWnd, szAtrPath,
                     OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST,
                     IDS_OPEN_DISK_IMAGE,
                     "ATR files\0*.ATR\0XFD files\0*.XFD\0All files\0*.*\0\0"))
          MainSectorsOpenFile(hWnd);
     return NULL;
}

static void MainSpyOpenFile(HWND hWnd)
{
AtariFile Info;
static BYTE szAddrSector[256];
static BYTE szNumSector[256];
static BYTE szLngSector[128];
static BYTE szSector[128];
WORD wAddr, wSec, wLng;
int iItem, nSeg, iSectorSize;
WORD wEnd;

      switch (AtariFindFirst(szSpyPath, &Info))
          {
          case ATARI_ERR_NO_ENTRY_FOUND:
          case ATARI_ERR_OK:
               AtariReadAbsoluteSector(szSpyPath, SPY_PATCH_SEC, szAddrSector, &iSectorSize);
               if (strncmp(&szAddrSector[1], SPY_MAGIC, strlen(SPY_MAGIC)))
                    Error(hWnd, IDS_NOT_SPY_DISK, "Not a spy disk !");
               else {
                    MainClean(TRUE);
                    AtariReadAbsoluteSector(szSpyPath, SPY_LNG_SEC, szLngSector, &iSectorSize);
                    AtariReadAbsoluteSector(szSpyPath, SPY_ADDR_SEC1, szAddrSector, &iSectorSize);
                    AtariReadAbsoluteSector(szSpyPath, SPY_ADDR_SEC2, &szAddrSector[128], &iSectorSize);
                    AtariReadAbsoluteSector(szSpyPath, SPY_NUM_SEC1, szNumSector, &iSectorSize);
                    AtariReadAbsoluteSector(szSpyPath, SPY_NUM_SEC2, &szNumSector[128], &iSectorSize);
                    nSeg = SegmentGetFirstFree();
                    for (iItem = 0; iItem < MAX_SPY_ADDR; iItem++)
                         {
                         if (nSeg < 0)
                              {
                              Error(hWnd, IDS_ERR_NO_FREE_SEG, "No more free segments (maximum is 256) !");
                              break;
                              }
                         wAddr = (WORD) szAddrSector[iItem * 2] + ((WORD) szAddrSector[1 + (iItem * 2)]) * 256;
                         if (wAddr == 0)
                              break;
                         wLng = (WORD) szLngSector[iItem];
                         wEnd = wAddr + wLng - 1;
                         wSec = (WORD) szNumSector[iItem * 2] + ((WORD) szNumSector[1 + (iItem * 2)]) * 256;
                         Segment[nSeg].lpDump = MemoryAlloc(wLng);
                         if (Segment[nSeg].lpDump == NULL)
                              {
                              Error(hWnd, IDS_ERR_NO_MEMORY, "Could not allocate memory !");
                              break;
                              }
                         Segment[nSeg].lpType = MemoryAlloc(wLng);
                         if (Segment[nSeg].lpType == NULL)
                              {
                              MemoryFree(Segment[nSeg].lpDump);
                              Segment[nSeg].lpDump = NULL;
                              Error(hWnd, IDS_ERR_NO_MEMORY, "Could not allocate memory !");
                              break;
                              }
                         Segment[nSeg].wBegin = wAddr;
                         Segment[nSeg].wEnd = wEnd;
                         Segment[nSeg].bBinary = TRUE;
                         AtariReadAbsoluteSector(szSpyPath, wSec, szSector, &iSectorSize);
                         memcpy(Segment[nSeg].lpDump, szSector, wLng);

                         /*
                         ** add this segment to the segment list.
                         */
                         wsprintf(szErr, "%04X-%04X:%04X (%u)", wAddr, wEnd, wLng, wLng);
                         SendMessage(hSegmentList, LB_ADDSTRING, 0, (LPARAM) (LPCSTR) szErr);
                         nSeg++;
                         if (nSeg >= MAX_SEGMENTS)
                              nSeg = -1;
                         }
                    SendMessage(hSegmentList, LB_SETCURSEL, 0, 0L);
                    SegmentSelected();
                    DisProcess(hInst, hWnd);
                    }
               break;

          case ATARI_ERR_DISK_NOT_FOUND:
               Error(hWnd, IDS_ERR_OPENING_FILE, "Error while opening file %s", szSpyPath);
               break;

          default:
               Error(hWnd, IDS_ERR_READING_ATR,
                     "Error reading file %s.\nEither the disk image file is corrupted or it is not an Atari single/enhanced density disk image file\nor the file contains more than 256 segments !",
                     szSpyPath);
               break;
          }
}

static long MainChooseSpyAtr(HWND hWnd)
{
     /*
     ** ask the user for a filename.
     */
     if (GetFileName(hWnd, szSpyPath,
                     OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST,
                     IDS_OPEN_SPY_IMAGE,
                     "ATR files\0*.ATR\0XFD files\0*.XFD\0All files\0*.*\0\0"))
          MainSpyOpenFile(hWnd);
     return NULL;
}

FILE *CreateIncludeFile(int nbFile, char *szPCFile)
{
char szBuf[128];
char *p;

     strcpy(szBuf, szPCFile);
     if ((p = strrchr(szPCFile, '.')) == NULL)
          p = &szBuf[strlen(szBuf)];
     else p = strrchr(szBuf, '.');
     wsprintf(p, ".%03d", nbFile);
     return fopen(szBuf, "w+t");
}

void InsertIncludeFile(FILE *fp, char *szAtariFile, DWORD dwLine, int nbFile)
{
char szBuf[128];
char *p;

     if (strlen(Config.config10.szComment))
          {
          szBuf[0] = 0;
          if (Config.config10.bLineNumbering)
               wsprintf(szBuf, "%lu ", dwLine);
          if (Config.config10.bAlignInstructions)
               strcat(szBuf, "         ");
          wsprintf(&szBuf[strlen(szBuf)], "%s%s", Config.config10.szINCLUDEHead, szAtariFile);
          if ((p = strrchr(szAtariFile, '.')) == NULL)
               p = &szBuf[strlen(szBuf)];
          else p = strrchr(szBuf, '.');
          wsprintf(p, ".%03d%s", nbFile, Config.config10.szINCLUDETail);
          fprintf(fp, "%s\n", szBuf);
          }
}

void InsertComment(FILE *fp, char *szComment, DWORD dwLine)
{
char szBuf[128];

     if (strlen(Config.config10.szComment))
          {
          if (Config.config10.bLineNumbering)
               wsprintf(szBuf, "%lu %s %s", dwLine, Config.config10.szComment, szComment);
          else wsprintf(szBuf, "%s %s", Config.config10.szComment, szComment);
          fprintf(fp, "%s\n", szBuf);
          }
}

static void SaveListingWithInclude(FILE *fp, char *szAtariFile, char *szPCFile)
{
DWORD dwLine;
WORD wNbListing;
WORD wLineInBuf;
char szBuf[128];
DIS_BUFFER *pBuf;
BYTE *pDis;
int nbFile, nLine;
FILE *fpInclude;

     dwLine = 0;
     nbFile = 1;
     if (Config.config10.bOneMainFile)
          fpInclude = CreateIncludeFile(nbFile, szPCFile);
     else fpInclude = fp;
     for (wNbListing = 0; wNbListing < MAX_DISASM; wNbListing++)
          {
          if (fpInclude == NULL)
               break;
          for (pBuf = Disasm[wNbListing]; pBuf; pBuf = pBuf->pNext)
               {
               if (fpInclude == NULL)
                    break;
               if (pDis = pBuf->pDis)
                    for (wLineInBuf = 0; wLineInBuf < pBuf->wNbLines; wLineInBuf++)
                         {
                         dwLine++;
                         if (Config.config10.bLineNumbering)
                              wsprintf(szBuf, "%lu %s", dwLine, pDis);
                         else strcpy(szBuf, pDis);
                         fprintf(fpInclude, "%s\n", szBuf);
                         pDis += strlen(pDis) + 1;
                         if (dwLine == (DWORD) Config.config10.wNbLinesPerInclude - 1)
                              {
                              nbFile++;
                              if (Config.config10.bOneMainFile == FALSE)
                                   {
                                   dwLine++;
                                   InsertComment(fpInclude, "", dwLine);
                                   dwLine++;
                                   InsertIncludeFile(fpInclude, szAtariFile, dwLine, nbFile);
                                   }
                              dwLine = 0;
                              if (fpInclude != fp)
                                   fclose(fpInclude);
                              fpInclude = CreateIncludeFile(nbFile, szPCFile);
                              if (fpInclude == NULL)
                                   break;
                              }
                         }
               }
          }
     if (Config.config10.bOneMainFile)
          {
          dwLine = 0;
          InsertComment(fp, "", ++dwLine);
          wsprintf(szBuf, "%s", szAtariFile);
          InsertComment(fp, szBuf, ++dwLine);
          InsertComment(fp, "", ++dwLine);
          for (nLine = 1; nLine <= nbFile; nLine++)
               InsertIncludeFile(fp, szAtariFile, ++dwLine, nLine);
          }
     else fp = fpInclude;
     if (fp)
          {
          if (Config.config10.bAlignInstructions)
               wsprintf(szBuf, "         %s%s%s", Config.config10.szENDHead, (Config.config10.bEndNeedFilename ? szAtariFile : ""), Config.config10.szENDTail);
          else wsprintf(szBuf, "%s%s%s", Config.config10.szENDHead, (Config.config10.bEndNeedFilename ? szAtariFile : ""), Config.config10.szENDTail);
          if (Config.config10.bLineNumbering)
               {
               fprintf(fp, "%lu %s\n", ++dwLine, Config.config10.szComment);
               fprintf(fp, "%lu %s\n", ++dwLine, szBuf);
               }
          else {
               fprintf(fp, "%s\n", Config.config10.szComment);
               fprintf(fp, "%s\n", szBuf);
               }
          }
     if (fpInclude)
          fclose(fpInclude);
}

static void SaveListing(FILE *fp, char *szAtariFile)
{
DWORD dwLine;
WORD wNbListing;
WORD wLineInBuf;
char szBuf[128];
DIS_BUFFER *pBuf;
BYTE *pDis;

     dwLine = 0;
     for (wNbListing = 0; wNbListing < MAX_DISASM; wNbListing++)
          for (pBuf = Disasm[wNbListing]; pBuf; pBuf = pBuf->pNext)
               if (pDis = pBuf->pDis)
                    for (wLineInBuf = 0; wLineInBuf < pBuf->wNbLines; wLineInBuf++)
                         {
                         dwLine++;
                         if (Config.config10.bLineNumbering)
                              wsprintf(szBuf, "%lu %s", dwLine, pDis);
                         else strcpy(szBuf, pDis);
                         fprintf(fp, "%s\n", szBuf);
                         pDis += strlen(pDis) + 1;
                         }
     if (Config.config10.bAlignInstructions)
          wsprintf(szBuf, "         %s%s%s", Config.config10.szENDHead, (Config.config10.bEndNeedFilename ? szAtariFile : ""), Config.config10.szENDTail);
     else wsprintf(szBuf, "%s%s%s", Config.config10.szENDHead, (Config.config10.bEndNeedFilename ? szAtariFile : ""), Config.config10.szENDTail);
     if (Config.config10.bLineNumbering)
          {
          fprintf(fp, "%lu %s\n", ++dwLine, Config.config10.szComment);
          fprintf(fp, "%lu %s\n", ++dwLine, szBuf);
          }
     else {
          fprintf(fp, "%s\n", Config.config10.szComment);
          fprintf(fp, "%s\n", szBuf);
          }
}

static long MainSaveListing(HWND hWnd)
{
char szAsmPath[_MAX_PATH];
FILE *fp;
char szAtariFile[20];
char *pSlash;

     memset(szAsmPath, 0, sizeof(szAsmPath));
     if (SaveFileName(hWnd, szAsmPath,
                      OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT,
                      IDS_SAVE_ASM_LISTING,
                      "ASM files\0*.ASM\0All files\0*.*\0\0"))
          {
          fp = fopen(szAsmPath, "w+t");
          if (fp != NULL)
               {
               strcpy(szAtariFile, "D:");
               if (pSlash = strrchr(szAsmPath, '\\'))
                    strcat(szAtariFile, pSlash + 1);
               else strcat(szAtariFile, szAsmPath);
               if (Config.config10.bIncludeAllowed == FALSE)
                    SaveListing(fp, szAtariFile);
               else SaveListingWithInclude(fp, szAtariFile, szAsmPath);
               fclose(fp);
               }
          }
     return NULL;
}

static long MainExit(HWND hWnd)
{
     PostMessage(hWnd, WM_CLOSE, 0, 0L);
     return NULL;
}

static long MainAbout(HWND hWnd)
{
     DialogBox(hInst, "About", hWnd, AboutProc);
     return NULL;
}

static int HexStringToAscii(HWND hDlg, char *szFindAscii, char *szFindHex, int nSizeofFindHex)
{
char szConv[3];
int nHex, i;

     GetDlgItemText(hDlg, IDC_FINDHEX, szFindHex, nSizeofFindHex - 1);
     szFindHex[nSizeofFindHex - 1] = 0;
     for (i = 0; i < (int) strlen(szFindHex); i += 2)
          {
          if (szFindHex[i + 1])
               {
               szConv[0] = szFindHex[i];
               szConv[1] = szFindHex[i + 1];
               }
          else {
               szConv[0] = '0';
               szConv[1] = szFindHex[i];
               }
          szConv[2] = 0;
          sscanf(szConv, "%02X", &nHex);
          szFindAscii[i / 2] = (char) nHex;
          }
     szFindAscii[i / 2] = 0;
     return (i / 2);
}

BOOL __export CALLBACK FindStringProc(HWND hDlg, UINT message, WORD wParam, LONG lParam)
{
char szFindHex[128+1];
char szFindAscii[64+1];
int i, nFindLength;
BOOL bFound, bWhole;
static BOOL bChange;

     switch (message)
          {
          case WM_INITDIALOG:
               szFindHex[0] = szFindAscii[0] = 0;
               DumpGetFindParam(szFindAscii, &nFindLength, &bWhole);
               for (i = 0; i < nFindLength; i++)
                    sprintf(&szFindHex[i * 2], "%02X", (unsigned char) szFindAscii[i]);
               szFindHex[i * 2] = 0;
               SendDlgItemMessage(hDlg, IDC_FINDHEX, EM_LIMITTEXT, sizeof(szFindHex) - 1, 0);
               SendDlgItemMessage(hDlg, IDC_FINDASCII, EM_LIMITTEXT, sizeof(szFindAscii) - 1, 0);
               bChange = TRUE;
               SetDlgItemText(hDlg, IDC_FINDHEX, szFindHex);
               SetDlgItemText(hDlg, IDC_FINDASCII, szFindAscii);
               bChange = FALSE;
               CheckRadioButton(hDlg, IDC_RADIO_WHOLE, IDC_RADIO_SELECTED, (bWhole ? IDC_RADIO_WHOLE : IDC_RADIO_SELECTED));
               EnableWindow(GetDlgItem(hDlg, IDOK), nFindLength);
               return TRUE;

          case WM_COMMAND:
               switch (wParam)
                    {
                    case IDC_FINDHEX:
                         if ((HIWORD(lParam) == EN_CHANGE) && (! bChange))
                              {
                              HexStringToAscii(hDlg, szFindAscii, szFindHex, sizeof(szFindHex));
                              bChange = TRUE;
                              SetDlgItemText(hDlg, IDC_FINDASCII, szFindAscii);
                              bChange = FALSE;
                              EnableWindow(GetDlgItem(hDlg, IDOK), strlen(szFindHex));
                              }
                         break;

                    case IDC_FINDASCII:
                         if ((HIWORD(lParam) == EN_CHANGE) && (! bChange))
                              {
                              GetDlgItemText(hDlg, IDC_FINDASCII, szFindAscii, sizeof(szFindAscii) - 1);
                              szFindAscii[sizeof(szFindAscii) - 1] = 0;
                              for (i = 0; i < (int) strlen(szFindAscii); i++)
                                   sprintf(&szFindHex[i * 2], "%02X", szFindAscii[i]);
                              szFindHex[i * 2] = 0;
                              bChange = TRUE;
                              SetDlgItemText(hDlg, IDC_FINDHEX, szFindHex);
                              bChange = FALSE;
                              EnableWindow(GetDlgItem(hDlg, IDOK), strlen(szFindHex));
                              }
                         break;

                    case IDCANCEL:
                         EndDialog(hDlg, FALSE);
                         return TRUE;

                    case IDOK:
                         i = HexStringToAscii(hDlg, szFindAscii, szFindHex, sizeof(szFindHex));
                         if (IsDlgButtonChecked(hDlg, IDC_RADIO_WHOLE))
                              bFound = DumpFindString(hDlg, szFindAscii, i, TRUE);
                         else bFound = DumpFindString(hDlg, szFindAscii, i, FALSE);
                         if (bFound)
                              EndDialog(hDlg, TRUE);
                         return TRUE;
                    }
               break;

          case WM_CTLCOLOR:

               /*
               ** change background color to gray.
               */
               if (HIWORD(lParam) == CTLCOLOR_STATIC || HIWORD(lParam) == CTLCOLOR_BTN || HIWORD(lParam) == CTLCOLOR_DLG)
                    {
                    SetBkColor((HDC) wParam, RGB(192, 192, 192));
                    return (BOOL) GetStockObject(LTGRAY_BRUSH);
                    }
               return FALSE;
          }
     return FALSE;
}

static long MainSelectAll(HWND hWnd)
{
     DumpSelectAll();
     return NULL;
}

static long MainFind(HWND hWnd)
{
     DialogBox(hInst, "FINDSTRING", hWnd, FindStringProc);
     return NULL;
}

static long MainFindNext(HWND hWnd)
{
     DumpFindNextString(hWnd);
     return NULL;
}

static long MainMergeSegments(HWND hWnd)
{
int nSeg;
WORD wSize;
WORD wFirstSize, wSecondSize;

     for (nSeg = 0; nSeg < MAX_SEGMENTS - 1; nSeg++)
          if (Segment[nSeg].lpDump && Segment[nSeg + 1].lpDump)
               if (Segment[nSeg].wEnd + 1 == Segment[nSeg + 1].wBegin)
                    {
                    wFirstSize = Segment[nSeg].wEnd - Segment[nSeg].wBegin + 1;
                    wSecondSize = Segment[nSeg + 1].wEnd - Segment[nSeg + 1].wBegin + 1;
                    if ((DWORD) wFirstSize + (DWORD) wSecondSize < 32768)
                         {
                         wSize = wFirstSize + wSecondSize;
                         if ((Segment[nSeg].lpDump = MemoryRealloc(Segment[nSeg].lpDump, wSize)) == NULL)
                              {
                              Error(hWnd, IDS_ERR_NO_MEMORY, "Could not allocate memory !");
                              SegmentUpdate();
                              return NULL;
                              }
                         if ((Segment[nSeg].lpType = MemoryRealloc(Segment[nSeg].lpType, wSize)) == NULL)
                              {
                              Error(hWnd, IDS_ERR_NO_MEMORY, "Could not allocate memory !");
                              SegmentUpdate();
                              return NULL;
                              }
                         memcpy(Segment[nSeg].lpDump + wFirstSize, Segment[nSeg + 1].lpDump, wSecondSize);
                         memcpy(Segment[nSeg].lpType + wFirstSize, Segment[nSeg + 1].lpType, wSecondSize);
                         MemoryFree(Segment[nSeg + 1].lpDump);
                         MemoryFree(Segment[nSeg + 1].lpType);
                         Segment[nSeg].wEnd = Segment[nSeg + 1].wEnd;
                         if (nSeg < MAX_SEGMENTS - 2)
                              memmove(&Segment[nSeg + 1], &Segment[nSeg + 2], sizeof(SEGMENT) * (MAX_SEGMENTS - 2 - nSeg));
                         memset(&Segment[MAX_SEGMENTS - 1], 0, sizeof(SEGMENT));
                         nSeg--;
                         }
                    }
     SegmentUpdate();
     return NULL;
}

static void WriteBootDisk(HWND hDlg, char *szPath)
{
AtariFile Info;
static BYTE szSector[128];
WORD wSize;
WORD wNbSec, wSec;
WORD wAddr, wInit;
WORD wOfs, wLng;
WORD wHeader;

      switch (AtariFindFirst(szPath, &Info))
          {
          case ATARI_ERR_NO_ENTRY_FOUND:
          case ATARI_ERR_OK:
               memset(szSector, 0, wHeader);
               wHeader = 6;
               wOfs = 0;
               wSize = Segment[wDumpSegment].wEnd - Segment[wDumpSegment].wBegin + 1;
               wNbSec = (wSize + wHeader + 127) / 128;

               GetDlgItemText(hDlg, IDC_BOOTLOADADDR, szErr, 5);
               sscanf(szErr, "%X", &wAddr);
               GetDlgItemText(hDlg, IDC_BOOTINITADDR, szErr, 5);
               sscanf(szErr, "%X", &wInit);

               szSector[0] = 0;
               szSector[1] = (BYTE) wNbSec;
               szSector[2] = (BYTE) (wAddr & 0xFF);
               szSector[3] = (BYTE) ((wAddr >> 8) & 0xFF);
               szSector[4] = (BYTE) (wInit & 0xFF);
               szSector[5] = (BYTE) ((wInit >> 8) & 0xFF);

               for (wSec = 1; wSec <= wNbSec; wSec++)
                    {
                    wLng = min(128 - wHeader, wSize);
                    memset(&szSector[wHeader], 0, 128 - wHeader);
                    memcpy(&szSector[wHeader], Segment[wDumpSegment].lpDump + wOfs, wLng);
                    AtariWriteAbsoluteSector(szPath, wSec, szSector);
                    wHeader = 0;
                    wOfs += wLng;
                    wSize -= wLng;
                    }
               break;

          case ATARI_ERR_DISK_NOT_FOUND:
               Error(hDlg, IDS_ERR_OPENING_FILE, "Error while opening file %s", szPath);
               break;

          default:
               Error(hDlg, IDS_ERR_READING_ATR,
                     "Error reading file %s.\nEither the disk image file is corrupted or it is not an Atari single/enhanced density disk image file\nor the file contains more than 256 segments !",
                     szPath);
               break;
          }
}

BOOL __export CALLBACK WriteBootProc(HWND hDlg, UINT message, WORD wParam, LONG lParam)
{
int nSeg;
WORD wAddr;
BOOL bSelected;
char szBootPath[_MAX_PATH];

     switch (message)
          {
          case WM_INITDIALOG:
               wAddr = Segment[wDumpSegment].wBegin;
               wsprintf(szErr, "%04X", wAddr);
               SetDlgItemText(hDlg, IDC_BOOTLOADADDR, szErr);
               for (nSeg = 0; nSeg < MAX_SEGMENTS; nSeg++)
                    if (Segment[nSeg].lpDump)
                         if (Segment[nSeg].wBegin == 0x02E0)
                              {
                              wAddr = (WORD) Segment[nSeg].lpDump[0] + (256 * (WORD) Segment[nSeg].lpDump[1]);
                              wsprintf(szErr, "%04X", wAddr);
                              SetDlgItemText(hDlg, IDC_BOOTINITADDR, szErr);
                              }
               return TRUE;

          case WM_COMMAND:
               switch (wParam)
                    {
                    case IDC_BOOTLOADADDR:
                    case IDC_BOOTINITADDR:
                         if (HIWORD(lParam) == EN_CHANGE)
                              {
                              bSelected = FALSE;
                              if (SendDlgItemMessage(hDlg, IDC_BOOTLOADADDR, WM_GETTEXTLENGTH, 0, 0) &&
                                 SendDlgItemMessage(hDlg, IDC_BOOTINITADDR, WM_GETTEXTLENGTH, 0, 0))
                                   bSelected = TRUE;
                              EnableWindow(GetDlgItem(hDlg, IDOK), bSelected);
                              }
                         return TRUE;

                    case IDOK:
                         memset(szBootPath, 0, sizeof(szBootPath));
                         if (SaveFileName(hDlg, szBootPath,
                                          OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT,
                                          IDS_SAVE_BOOT_IMAGE,
                                          "ATR files\0*.ATR\0XFD files\0*.XFD\0All files\0*.*\0\0"))
                              {
                              WriteBootDisk(hDlg, szBootPath);
                              EndDialog(hDlg, TRUE);
                              }
                         return TRUE;

                    case IDCANCEL:
                         EndDialog(hDlg, FALSE);
                         return TRUE;
                    }
               break;

          case WM_CTLCOLOR:

               /*
               ** change background color to gray.
               */
               if (HIWORD(lParam) == CTLCOLOR_STATIC || HIWORD(lParam) == CTLCOLOR_BTN || HIWORD(lParam) == CTLCOLOR_DLG)
                    {
                    SetBkColor((HDC) wParam, RGB(192, 192, 192));
                    return (BOOL) GetStockObject(LTGRAY_BRUSH);
                    }
               return FALSE;
          }
     return FALSE;
}

static long MainWriteBootDisk(HWND hWnd)
{
     if (wDumpSegment == NO_DUMP)
          {
          Error(hWnd, IDS_ERR_NO_SEGMENT, "There is no segment in memory !");
          return NULL;
          }

     DialogBox(hInst, "WRITEBOOTBOX", hWnd, WriteBootProc);
     return NULL;
}

static void SpyLoad(HWND hDlg)
{
AtariFile Info;
static BYTE szAddrSector[256];
static BYTE szLngSector[128];
WORD wAddr, wLng;
int iItem, iNbItem, iSectorSize;

      switch (AtariFindFirst(szSpyPath, &Info))
          {
          case ATARI_ERR_NO_ENTRY_FOUND:
          case ATARI_ERR_OK:
               AtariReadAbsoluteSector(szSpyPath, SPY_PATCH_SEC, szAddrSector, &iSectorSize);
               if (strncmp(&szAddrSector[1], SPY_MAGIC, strlen(SPY_MAGIC)))
                    Error(hDlg, IDS_NOT_SPY_DISK, "Not a spy disk !");
               else {
                    if (szAddrSector[0])
                         CheckDlgButton(hDlg, IDC_CHECKSPY, 1);
                    else CheckDlgButton(hDlg, IDC_CHECKSPY, 0);
                    for (iItem = 0; iItem < 20; iItem++)
                         {
                         if (szAddrSector[4 + iItem] < 64)
                              szAddrSector[4 + iItem] += 32;
                         else if (szAddrSector[4 + iItem] < 96)
                              szAddrSector[4 + iItem] -= 64;
                         else if ((szAddrSector[4 + iItem] >= 128) && (szAddrSector[4 + iItem] < 128 + 64))
                              szAddrSector[4 + iItem] += 32;
                         else if ((szAddrSector[4 + iItem] >= 128 + 64) && (szAddrSector[4 + iItem] < 128 + 96))
                              szAddrSector[4 + iItem] -= 64;
                         }
                    memcpy(szErr, &szAddrSector[4], 20);
                    szErr[20] = 0;
                    SetDlgItemText(hDlg, IDC_PATCHTITLE, szErr);
                    SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_RESETCONTENT, 0, 0);
                    iItem = MAX_PATCHES;
                    do   {
                         iItem--;
                         wAddr = (WORD) szAddrSector[24 + (iItem * 2)] + ((WORD) szAddrSector[25 + (iItem * 2)]) * 256;
                         }
                    while (wAddr == 0 && iItem >= 0);
                    iNbItem = iItem;
                    for (iItem = 0; iItem <= iNbItem; iItem++)
                         {
                         wAddr = (WORD) szAddrSector[24 + (iItem * 2)] + ((WORD) szAddrSector[25 + (iItem * 2)]) * 256;
                         wsprintf(szErr, "%04X", wAddr);
                         SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_ADDSTRING, 0, (LPARAM) (LPCSTR) szErr);
                         }
                    SetDlgItemText(hDlg, IDC_COMBOPATCH, "");
                    EnableWindow(GetDlgItem(hDlg, IDC_REMOVESPY), FALSE);
                    EnableWindow(GetDlgItem(hDlg, IDC_ADDSPY), FALSE);
                    AtariReadAbsoluteSector(szSpyPath, SPY_ADDR_SEC1, szAddrSector, &iSectorSize);
                    AtariReadAbsoluteSector(szSpyPath, SPY_ADDR_SEC2, &szAddrSector[128], &iSectorSize);
                    AtariReadAbsoluteSector(szSpyPath, SPY_LNG_SEC, szLngSector, &iSectorSize);
                    SendDlgItemMessage(hDlg, IDC_LISTMEMSPY, LB_RESETCONTENT, 0, 0);
                    for (iItem = 0; iItem < MAX_SPY_ADDR; iItem++)
                         {
                         wAddr = (WORD) szAddrSector[iItem * 2] + ((WORD) szAddrSector[1 + (iItem * 2)]) * 256;
                         if (wAddr == 0)
                              break;
                         wLng = (WORD) szLngSector[iItem];
                         wsprintf(szErr, "%04X %u", wAddr, wLng);
                         SendDlgItemMessage(hDlg, IDC_LISTMEMSPY, LB_ADDSTRING, 0, (LPARAM) (LPCSTR) szErr);
                         }
                    SetDlgItemText(hDlg, IDC_MEMADDR, "");
                    SetDlgItemText(hDlg, IDC_MEMLEN, "");
                    EnableWindow(GetDlgItem(hDlg, IDC_REMOVEMEMSPY), FALSE);
                    EnableWindow(GetDlgItem(hDlg, IDC_ADDMEMSPY), FALSE);
                    }
               break;

          case ATARI_ERR_DISK_NOT_FOUND:
               Error(hDlg, IDS_ERR_OPENING_FILE, "Error while opening file %s", szSpyPath);
               break;

          default:
               Error(hDlg, IDS_ERR_READING_ATR,
                     "Error reading file %s.\nEither the disk image file is corrupted or it is not an Atari single/enhanced density disk image file\nor the file contains more than 256 segments !",
                     szSpyPath);
               break;
          }
}

static void SpySave(HWND hDlg)
{
AtariFile Info;
static BYTE szAddrSector[256];
static BYTE szNumSector[256];
static BYTE szLngSector[128];
WORD wAddr, wLng, wSec;
int iItem, iNbItem;

      switch (AtariFindFirst(szSpyPath, &Info))
          {
          case ATARI_ERR_NO_ENTRY_FOUND:
          case ATARI_ERR_OK:
               memset(szAddrSector, 0, sizeof(szAddrSector));
               szAddrSector[0] = (BYTE) IsDlgButtonChecked(hDlg, IDC_CHECKSPY);
               strcpy(&szAddrSector[1], SPY_MAGIC);
               GetDlgItemText(hDlg, IDC_PATCHTITLE, szErr, 21);
               for (iItem = 0; iItem < 20; iItem++)
                    {
                    if (szErr[iItem] == 0)
                         break;
                    if ((szErr[iItem] >= 32) && (szErr[iItem] < 96))
                         szErr[iItem] -= 32;
                    else if (szErr[iItem] < 32)
                         szErr[iItem] += 64;
                    else if ((szErr[iItem] >= (128 + 32)) && (szErr[iItem] < (128 + 96)))
                         szErr[iItem] -= 32;
                    else if (szErr[iItem] < (128 + 32))
                         szErr[iItem] += 64;
                    szAddrSector[4 + iItem] = szErr[iItem];
                    }
               iNbItem = (int) SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_GETCOUNT, 0, 0);
               for (iItem = 0; iItem < min(iNbItem, MAX_PATCHES); iItem++)
                    {
                    SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_GETLBTEXT, iItem, (LPARAM) (LPCSTR) szErr);
                    sscanf(szErr, "%X", &wAddr);
                    szAddrSector[24 + (iItem * 2)] = LOBYTE(wAddr);
                    szAddrSector[25 + (iItem * 2)] = HIBYTE(wAddr);
                    }
               AtariWriteAbsoluteSector(szSpyPath, SPY_PATCH_SEC, szAddrSector);
               memset(szAddrSector, 0, sizeof(szAddrSector));
               memset(szNumSector, 0, sizeof(szNumSector));
               memset(szLngSector, 0, sizeof(szLngSector));
               iNbItem = (int) SendDlgItemMessage(hDlg, IDC_LISTMEMSPY, LB_GETCOUNT, 0, 0);
               wSec = SPY_DATA_SEC;
               for (iItem = 0; iItem < min(iNbItem, MAX_SPY_ADDR); iItem++)
                    {
                    SendDlgItemMessage(hDlg, IDC_LISTMEMSPY, LB_GETTEXT, iItem, (LPARAM) (LPCSTR) szErr);
                    sscanf(szErr, "%04X %u", &wAddr, &wLng);
                    szLngSector[iItem] = (BYTE) wLng;
                    szAddrSector[(iItem * 2)] = LOBYTE(wAddr);
                    szAddrSector[(iItem * 2) + 1] = HIBYTE(wAddr);
                    szNumSector[(iItem * 2)] = LOBYTE(wSec);
                    szNumSector[(iItem * 2) + 1] = HIBYTE(wSec);
                    wSec++;
                    }
               AtariWriteAbsoluteSector(szSpyPath, SPY_LNG_SEC, szLngSector);
               AtariWriteAbsoluteSector(szSpyPath, SPY_ADDR_SEC1, szAddrSector);
               AtariWriteAbsoluteSector(szSpyPath, SPY_ADDR_SEC2, &szAddrSector[128]);
               AtariWriteAbsoluteSector(szSpyPath, SPY_NUM_SEC1, szNumSector);
               AtariWriteAbsoluteSector(szSpyPath, SPY_NUM_SEC2, &szNumSector[128]);
               break;

          case ATARI_ERR_DISK_NOT_FOUND:
               Error(hDlg, IDS_ERR_OPENING_FILE, "Error while opening file %s", szSpyPath);
               break;

          default:
               Error(hDlg, IDS_ERR_READING_ATR,
                     "Error reading file %s.\nEither the disk image file is corrupted or it is not an Atari single/enhanced density disk image file\nor the file contains more than 256 segments !",
                     szSpyPath);
               break;
          }
}

BOOL __export CALLBACK SetSpyProc(HWND hDlg, UINT message, WORD wParam, LONG lParam)
{
int iItem, iNbItem;
BOOL bSelected;
BOOL bTrans;
WORD wAddr, wLen;

     switch (message)
          {
          case WM_INITDIALOG:
               SendDlgItemMessage(hDlg, IDC_PATCHTITLE, EM_LIMITTEXT, 20, 0);
               SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_LIMITTEXT, 4, 0);
               SendDlgItemMessage(hDlg, IDC_MEMADDR, CB_LIMITTEXT, 4, 0);
               SendDlgItemMessage(hDlg, IDC_MEMLEN, CB_LIMITTEXT, 3, 0);
               EnableWindow(GetDlgItem(hDlg, IDC_REMOVESPY), FALSE);
               EnableWindow(GetDlgItem(hDlg, IDC_ADDSPY), FALSE);
               EnableWindow(GetDlgItem(hDlg, IDC_REMOVEMEMSPY), FALSE);
               EnableWindow(GetDlgItem(hDlg, IDC_ADDMEMSPY), FALSE);
               return TRUE;

          case WM_COMMAND:
               switch (wParam)
                    {
                    case IDCANCEL:
                         EndDialog(hDlg, TRUE);
                         return TRUE;

                    case IDC_ADDSPY:
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_GETCOUNT, 0, 0);
                         if (iNbItem >= MAX_PATCHES)
                              {
                              Error(hDlg, IDS_ERR_TOO_MANY_PATCHES, "Too many patches !");
                              return TRUE;
                              }
                         if (SendDlgItemMessage(hDlg, IDC_COMBOPATCH, WM_GETTEXTLENGTH, 0, 0))
                              {
                              GetDlgItemText(hDlg, IDC_COMBOPATCH, szErr, 5);
                              sscanf(szErr, "%X", &wAddr);
                              wsprintf(szErr, "%04X", wAddr);
                              SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_ADDSTRING, 0, (LPARAM) (LPCSTR) szErr);
                              SetDlgItemText(hDlg, IDC_COMBOPATCH, "");
                              EnableWindow(GetDlgItem(hDlg, IDC_ADDSPY), FALSE);
                              }
                         return TRUE;

                    case IDC_REMOVESPY:
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_GETCURSEL, 0, 0);
                         if (iNbItem != CB_ERR)
                              SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_DELETESTRING, iNbItem, 0);
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_GETCURSEL, 0, 0);
                         EnableWindow(GetDlgItem(hDlg, IDC_REMOVESPY), (iNbItem != CB_ERR));
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_COMBOPATCH, WM_GETTEXTLENGTH, 0, 0);
                         EnableWindow(GetDlgItem(hDlg, IDC_ADDSPY), (iNbItem > 0));
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_GETCOUNT, 0, 0);
                         return TRUE;

                    case IDC_COMBOPATCH:
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_GETCOUNT, 0, 0);
                         if (HIWORD(lParam) == CBN_SELCHANGE)
                              {
                              bSelected = FALSE;
                              if (SendDlgItemMessage(hDlg, IDC_COMBOPATCH, CB_GETCURSEL, 0, 0) != CB_ERR)
                                   bSelected = TRUE;
                              EnableWindow(GetDlgItem(hDlg, IDC_REMOVESPY), bSelected);
                              }
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_COMBOPATCH, WM_GETTEXTLENGTH, 0, 0);
                         EnableWindow(GetDlgItem(hDlg, IDC_ADDSPY), (iNbItem > 0));
                         return TRUE;

                    case IDC_ADDMEMSPY:
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_LISTMEMSPY, LB_GETCOUNT, 0, 0);
                         if (iNbItem >= MAX_SPY_ADDR)
                              {
                              Error(hDlg, IDS_ERR_TOO_MANY_SPY, "Too many addresses to spy !");
                              break;
                              }
                         wLen = GetDlgItemInt(hDlg, IDC_MEMLEN, &bTrans, FALSE);
                         if (wLen > 128)
                              {
                              Error(hDlg, IDS_ERR_BAD_SPY_LEN, "Length should not be greater than 128 (sector size) !");
                              SetFocus(GetDlgItem(hDlg, IDC_MEMLEN));
                              break;
                              }
                         GetDlgItemText(hDlg, IDC_MEMADDR, szErr, 5);
                         sscanf(szErr, "%X", &wAddr);
                         wsprintf(szErr, "%04X %u", wAddr, wLen);
                         SendDlgItemMessage(hDlg, IDC_LISTMEMSPY, LB_ADDSTRING, 0, (LPARAM) (LPCSTR) szErr);
                         wAddr += wLen;
                         wsprintf(szErr, "%04X", wAddr);
                         SetDlgItemText(hDlg, IDC_MEMADDR, szErr);
                         return TRUE;

                    case IDC_REMOVEMEMSPY:
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_LISTMEMSPY, LB_GETCOUNT, 0, 0);
                         for (iItem = iNbItem - 1; iItem >= 0; iItem--)
                              if (SendDlgItemMessage(hDlg, IDC_LISTMEMSPY, LB_GETSEL, iItem, 0))
                                   SendDlgItemMessage(hDlg, IDC_LISTMEMSPY, LB_DELETESTRING, iItem, 0);
                         EnableWindow(GetDlgItem(hDlg, IDC_REMOVEMEMSPY), FALSE);
                         return TRUE;

                    case IDC_MEMADDR:
                    case IDC_MEMLEN:
                         if (HIWORD(lParam) == EN_CHANGE)
                              {
                              bSelected = FALSE;
                              if (SendDlgItemMessage(hDlg, IDC_MEMADDR, WM_GETTEXTLENGTH, 0, 0) &&
                                 SendDlgItemMessage(hDlg, IDC_MEMLEN, WM_GETTEXTLENGTH, 0, 0))
                                   bSelected = TRUE;
                              EnableWindow(GetDlgItem(hDlg, IDC_ADDMEMSPY), bSelected);
                              }
                         return TRUE;

                    case IDC_LISTMEMSPY:
                         iNbItem = (int) SendDlgItemMessage(hDlg, IDC_LISTMEMSPY, LB_GETCOUNT, 0, 0);
                         if (HIWORD(lParam) == LBN_SELCHANGE)
                              {
                              bSelected = FALSE;
                              for (iItem = 0; iItem < iNbItem; iItem++)
                                   if (SendDlgItemMessage(hDlg, IDC_LISTMEMSPY, LB_GETSEL, iItem, 0))
                                        {
                                        bSelected = TRUE;
                                        break;
                                        }
                              EnableWindow(GetDlgItem(hDlg, IDC_REMOVEMEMSPY), bSelected);
                              }
                         return TRUE;

                    case IDC_LOADSPY:
                         if (GetFileName(hDlg, szSpyPath,
                                         OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST,
                                         IDS_OPEN_SPY_IMAGE,
                                         "ATR files\0*.ATR\0XFD files\0*.XFD\0All files\0*.*\0\0"))
                              SpyLoad(hDlg);
                         return TRUE;

                    case IDC_SAVESPY:
                         if (SaveFileName(hDlg, szSpyPath,
                                          OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT,
                                          IDS_SAVE_SPY_IMAGE,
                                          "ATR files\0*.ATR\0XFD files\0*.XFD\0All files\0*.*\0\0"))
                              SpySave(hDlg);
                         return TRUE;
                    }
               break;

          case WM_CTLCOLOR:

               /*
               ** change background color to gray.
               */
               if (HIWORD(lParam) == CTLCOLOR_STATIC || HIWORD(lParam) == CTLCOLOR_BTN || HIWORD(lParam) == CTLCOLOR_DLG)
                    {
                    SetBkColor((HDC) wParam, RGB(192, 192, 192));
                    return (BOOL) GetStockObject(LTGRAY_BRUSH);
                    }
               return FALSE;
          }
     return FALSE;
}

static long MainPrepareSpyDisk(HWND hWnd)
{
     DialogBox(hInst, "SETSPYBOX", hWnd, SetSpyProc);
     return NULL;
}

long CALLBACK __export MainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
WORD wEnableMenu;

     switch (message)
          {
          case WM_CREATE:
               return MainCreate(hWnd);

          case WM_PAINT:
               return MainPaint(hWnd);

          case WM_INITMENU:
               wEnableMenu = (*CompuGetMenu)();
               CheckMenuItem(GetSubMenu(GetMenu(hWnd), 4), ID_OPTIONS_SCREENBYTEDUMP, MF_BYCOMMAND | (bConfigDumpInternal ? MF_CHECKED : MF_UNCHECKED));
               CheckMenuItem(GetSubMenu(GetMenu(hWnd), 4), ID_OPTIONS_NODISASSEMBLY, MF_BYCOMMAND | (bConfigNoDisassembly ? MF_CHECKED : MF_UNCHECKED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 3), ID_TOOLS_MERGESEGMENTS, MF_BYCOMMAND | ((wDumpSegment != NO_DUMP) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 3), ID_TOOLS_WRITEBOOTDISK, MF_BYCOMMAND | ((wDumpSegment != NO_DUMP) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 1), ID_EDIT_SELECTSPRITES, MF_BYCOMMAND | ((wDumpSegment != NO_DUMP) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 1), ID_EDIT_SELECTALL, MF_BYCOMMAND | ((wDumpSegment != NO_DUMP) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 1), ID_EDIT_FIND, MF_BYCOMMAND | (DumpCanFind(TRUE) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 1), ID_EDIT_FINDNEXT, MF_BYCOMMAND | (DumpCanFind(FALSE) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 2), ID_LABELS_CLEAR, MF_BYCOMMAND | (lpLabelUser ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 2), ID_LABELS_SAVE, MF_BYCOMMAND | (lpLabelUser ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_SAVEWORKSPACE, MF_BYCOMMAND | ((SegmentGetCount()) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_OPEN, ((wEnableMenu & MENU_BINARY_FILE) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_OPEN_ATR, ((wEnableMenu & MENU_DISK_FILE) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_OPENDISKIMAGEBOOT, ((wEnableMenu & MENU_DISK_BOOT) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_OPENDISKIMAGESECTORS, ((wEnableMenu & MENU_DISK_SECTORS) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_OPENSPYDISKIMAGEDATA, ((wEnableMenu & MENU_SPY) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 3), ID_TOOLS_PREPARESPYDISK, ((wEnableMenu & MENU_SPY) ? MF_ENABLED : MF_GRAYED));
               EnableMenuItem(GetSubMenu(GetMenu(hWnd), 3), ID_TOOLS_DISKIMAGEPCTRANSFER, ((wEnableMenu & MENU_TRANSFER) ? MF_ENABLED : MF_GRAYED));
               return NULL;

          case WM_COMMAND:
               switch (wParam)
                    {
                    case IDM_ABOUT:
                         return MainAbout(hWnd);

                    case ID_FILE_NEW:
                         MainClean(TRUE);
                         return NULL;

                    case ID_FILE_OPEN:
                         return MainChooseFile(hWnd);

                    case ID_FILE_OPEN_ATR:
                         return MainChooseAtrDisk(hWnd);

                    case ID_FILE_OPENDISKIMAGEBOOT:
                         return MainChooseAtrBoot(hWnd);

                    case ID_FILE_OPENDISKIMAGESECTORS:
                         return MainChooseAtrSectors(hWnd);

                    case ID_FILE_OPENSPYDISKIMAGEDATA:
                         return MainChooseSpyAtr(hWnd);

                    case ID_FILE_LOADWORKSPACE:
                         WorkspaceLoad(hWnd);
                         return NULL;

                    case ID_FILE_SAVEWORKSPACE:
                         WorkspaceSave(hWnd);
                         return NULL;

                    case ID_FILE_SAVEDISASSEMBLYLISTING:
                         return MainSaveListing(hWnd);

                    case ID_FILE_QUIT:
                         return MainExit(hWnd);

                    case ID_EDIT_SELECTSPRITES:
                    case IDM_DUMP_FIND_SPRITES:
                         if (Segment[wDumpSegment].lpDump)
                              DumpSprites(hWnd);
                         break;

                    case ID_EDIT_SELECTALL:
                    case IDM_DUMP_SELECTALL:
                         return MainSelectAll(hWnd);

                    case ID_EDIT_FIND:
                    case IDM_DUMP_FIND:
                         return MainFind(hWnd);

                    case ID_EDIT_FINDNEXT:
                    case IDM_DUMP_FIND_NEXT:
                         return MainFindNext(hWnd);

                    case ID_LABELS_CLEAR:
                         if (LabelUserClear(hInst, hWnd))
                              if (! bConfigNoDisassembly)
                                   DumpDisassemble(hWnd);
                         return NULL;

                    case ID_LABELS_EDIT:
                         if (LabelUserEdit(hInst, hWnd))
                              if (! bConfigNoDisassembly)
                                   DumpDisassemble(hWnd);
                         return NULL;

                    case ID_LABELS_LOAD:
                         if (LabelUserLoad(hInst, hWnd))
                              if (! bConfigNoDisassembly)
                                   DumpDisassemble(hWnd);
                         return NULL;

                    case ID_LABELS_SAVE:
                         LabelUserSave(hInst, hWnd);
                         return NULL;

                    case ID_TOOLS_MERGESEGMENTS:
                         return MainMergeSegments(hWnd);

                    case ID_TOOLS_WRITEBOOTDISK:
                         return MainWriteBootDisk(hWnd);

                    case ID_TOOLS_PREPARESPYDISK:
                         return MainPrepareSpyDisk(hWnd);

                    case ID_TOOLS_DISKIMAGEPCTRANSFER:
                         return TransferFile(hWnd);

                    case ID_OPTIONS_OUTPUTFORMAT:
                         return ConfigOutputFormat(hWnd);

                    case ID_OPTIONS_ASSEMBLERFORMAT:
                         return ConfigAsmFormat(hWnd);

                    case ID_SEGMENT:
                         return MainSegment(hWnd, lParam);

                    case ID_DUMP:
                         return MainDump(hWnd, lParam);

                    case ID_OPTIONS_SCREENBYTEDUMP:
                    case IDM_DUMP_DISPLAY:
                         DumpSetInternal(hWnd);
                         break;

                    case ID_OPTIONS_NODISASSEMBLY:
                    case IDM_DUMP_NO_DIS:
                         bConfigNoDisassembly = ! bConfigNoDisassembly;
                         if (! bConfigNoDisassembly)
                              DumpDisassemble(hWnd);
                         break;

                    case IDM_DUMP_TYPE_CODE:
                    case IDM_DUMP_TYPE_LOBYTE:
                    case IDM_DUMP_TYPE_HIBYTE:
                    case IDM_DUMP_TYPE_BYTE:
                    case IDM_DUMP_TYPE_WORD:
                    case IDM_DUMP_TYPE_LABEL:
                    case IDM_DUMP_TYPE_STRING:
                    case IDM_DUMP_TYPE_SBYTE:
                    case IDM_DUMP_TYPE_DLIST:
                    case IDM_DUMP_TYPE_STORE:
                         DumpSetType(hWnd, wParam - IDM_DUMP_TYPE_CODE);
                         break;

                    default:
                         return DefWindowProc(hWnd, message, wParam, lParam);
                    }
               break;

          case WM_OPENCMDLINE:
               if (*szAtrPath)
                    return MainDiskOpenFile(hWnd);
               else return MainOpenFile(hWnd);

          case WM_DESTROY:
               hMainWnd = hSegmentList = hDumpWnd = hDisWnd = 0;
               PostQuitMessage(0);
               return NULL;

          default:
               return DefWindowProc(hWnd, message, wParam, lParam);
          }
     return NULL;
}

BOOL InitInstance(HANDLE hInstance, int nCmdShow)
{
     /*
     ** Save the instance handle in static variable, which will be used in
     ** many subsequence calls from this application to Windows.
     */
     hInst = hInstance;

     /*
     ** Create a main window for this application instance.
     */
     hMainWnd = CreateWindow("6502Class", szTitleApp,
                             WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
                             CW_USEDEFAULT, CW_USEDEFAULT, MAIN_WIDTH, MAIN_HEIGHT,
                             NULL, NULL, hInstance, NULL);

     /*
     ** If window could not be created, return "failure"
     */
     if (! hMainWnd)
          return FALSE;

     /*
     ** Create segment listbox.
     */
     hSegmentList = CreateWindow("ListBox", "",
                                 WS_VSCROLL | WS_BORDER | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS |
                                 LBS_NOINTEGRALHEIGHT | LBS_NOTIFY | LBS_DISABLENOSCROLL,
                                 SEGMENT_LEFT, SEGMENT_TOP, SEGMENT_WIDTH, SEGMENT_HEIGHT,
                                 hMainWnd, ID_SEGMENT, hInstance, NULL);
     SendMessage(hSegmentList, WM_SETFONT, hComputerFont, (LPARAM) MAKELONG(0, FALSE));

     /*
     ** Create dump window.
     */
     hDumpWnd = CreateWindow("DumpCtrlClass", "",
                             WS_VSCROLL | WS_BORDER | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS,
                             DUMP_LEFT, DUMP_TOP, DUMP_WIDTH, DUMP_HEIGHT,
                             hMainWnd, ID_DUMP, hInstance, NULL);
     SendMessage(hDumpWnd, WM_SETFONT, hComputerFont, (LPARAM) MAKELONG(0, FALSE));

     /*
     ** Create disassembly window.
     */
     hDisWnd = CreateWindow("DisCtrlClass", "",
                            WS_VSCROLL | WS_BORDER | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_DISABLED,
                            DIS_LEFT, DIS_TOP, DIS_WIDTH, DIS_HEIGHT,
                            hMainWnd, ID_DIS, hInstance, NULL);
     SendMessage(hDisWnd, WM_SETFONT, hComputerFont, (LPARAM) MAKELONG(0, FALSE));
     SendMessage(hDisWnd, WM_DIS_SET_BUFFER, MAX_DISASM, (LPARAM) (LPVOID) &Disasm);

     /*
     ** Make the window visible; update its client area; and return "success"
     */
     ShowWindow(hMainWnd, nCmdShow);
     UpdateWindow(hMainWnd);
     return TRUE;
}

static void TermApplication(void)
{
     if (hComputerInst >= HINSTANCE_ERROR)
          {
          (*CompuTerm)();
          FreeLibrary(hComputerInst);
          }
     hComputerInst = 0;
}

BOOL InitApplication(HANDLE hInstance, LPSTR lpCmdLine)
{
WNDCLASS wc;
char *szSlash;
UINT uOldMode;
BOOL bRet;

     /*
     ** load common strings.
     */
     if (LoadString(hInstance, IDS_TITLE_APP, szTitleApp, sizeof(szTitleApp)) == 0)
          strcpy(szTitleApp, "6502 Disassembler for ");
     if (LoadString(hInstance, IDS_SEGMENT_TITLE, szSegmentTitle, sizeof(szSegmentTitle)) == 0)
          strcpy(szSegmentTitle, "Segments for %s");
     if (LoadString(hInstance, IDS_NO_SEGMENT_TITLE, szNoSegmentTitle, sizeof(szNoSegmentTitle)) == 0)
          strcpy(szNoSegmentTitle, "No file loaded");
     if (LoadString(hInstance, IDS_DUMP_TITLE, szDumpTitle, sizeof(szDumpTitle)) == 0)
          strcpy(szDumpTitle, "Dump of segment $%04X-$%04X:%u");
     if (LoadString(hInstance, IDS_NO_DUMP_TITLE, szNoDumpTitle, sizeof(szNoDumpTitle)) == 0)
          strcpy(szNoDumpTitle, "No dump");
     if (LoadString(hInstance, IDS_DIS_TITLE, szDisassembly, sizeof(szDisassembly)) == 0)
          strcpy(szDisassembly, "Disassembly");

     /*
     ** build computer library path (ATARI, ORIC,...)
     */
     if (*lpCmdLine == '/')
          {
          strncpy(szComputerName, lpCmdLine + 1, sizeof(szComputerName) - 1);
          *lpCmdLine = 0;
          }
     else strcpy(szComputerName, "Atari");
     strcat(szTitleApp, szComputerName);
     GetModuleFileName(hInstance, szComputerDll, sizeof(szComputerDll));
     if (szSlash = strrchr(szComputerDll, '\\'))
          strcpy(szSlash + 1, szComputerName);
     else strcpy(szComputerDll, szComputerName);
     strcpy(szComputerEqu, szComputerDll);
     strcat(szComputerDll, "SYS.DLL");
     strcat(szComputerEqu, ".EQU");

     /*
     ** load computer library
     */
     uOldMode = SetErrorMode(SEM_NOOPENFILEERRORBOX);
     hComputerInst = LoadLibrary(szComputerDll);
     SetErrorMode(uOldMode);
     switch (hComputerInst)
          {
          case 0:
          case 8:
               Error(0, IDS_COMPUTER_MEM_ERR, "Not enough memory to load computer file %s", szComputerDll);
               return FALSE;
          case 2:
          case 3:
               Error(0, IDS_COMPUTER_NOT_FOUND, "Computer file %s was not found", szComputerDll);
               return FALSE;
          default:
               if (hComputerInst < HINSTANCE_ERROR)
                    {
                    Error(0, IDS_COMPUTER_NOT_LOADED, "Computer file %s can not be loaded", szComputerDll);
                    return FALSE;
                    }

               /*
               ** Get all entry points.
               */
               CompuInit = (COMPUINIT) GetProcAddress(hComputerInst, "CompuInit");
               CompuTerm = (COMPUTERM) GetProcAddress(hComputerInst, "CompuTerm");
               CompuGetMenu = (COMPUGETMENU) GetProcAddress(hComputerInst, "CompuGetMenu");
               CompuLoadSegment = (COMPULOADSEGMENT) GetProcAddress(hComputerInst, "CompuLoadSegment");
               CompuUpdateSegment = (COMPUUPDATESEGMENT) GetProcAddress(hComputerInst, "CompuUpdateSegment");
               CompuReadFile = (COMPUREADFILE) GetProcAddress(hComputerInst, "CompuReadFile");
               CompuGetReturn = (COMPUGETRETURN) GetProcAddress(hComputerInst, "CompuGetReturn");
               if (CompuInit == 0 || CompuTerm == 0 || CompuGetMenu == 0 || CompuLoadSegment == 0 || CompuUpdateSegment == 0 || CompuReadFile == 0 || CompuGetReturn == 0)
                    {
                    Error(0, IDS_ENTRY_NOT_FOUND, "File %s has not all entry points expected", szComputerDll);
                    FreeLibrary(hComputerInst);
                    hComputerInst = 0;
                    return FALSE;
                    }

               /*
               ** Call ComputInit to set up computer DLL.
               */
               if ((*CompuInit)(hInstance) == 0)
                    Error(0, IDS_FNT_NOT_FOUND, "Font could not be loaded by %s\nUsing default font", szComputerDll);
               else bFontOK = TRUE;
               cReturn = (*CompuGetReturn)();
               break;
          }

     /*
     ** initialize label and dump module
     */
     strcpy(szBinPath, "");
     strcpy(szAtrPath, "");
     ConfigInit();
     LabelInit();
     DumpRegister(hInstance);
     DisRegister(hInstance);

     /*
     ** Fill in window class structure with parameters that describe the
     ** main window.
     */
     wc.style = NULL;
     wc.lpfnWndProc = MainWndProc;
     wc.cbClsExtra = 0;
     wc.cbWndExtra = 0;
     wc.hInstance = hInstance;
     wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_DIS6502));
     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
     wc.hbrBackground = GetStockObject(WHITE_BRUSH);
     wc.lpszMenuName = "Dis6502Menu";
     wc.lpszClassName = "6502Class";

     /*
     ** Register the window class and return success/failure code.
     */
     bRet = RegisterClass(&wc);
     if (bRet == FALSE)
          TermApplication();
     return bRet;
}

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG  msg;
HWND hWndPrevApp;
char *p;
HACCEL hAccel;

     /*
     ** if he program is already running, we activate the previous
     ** instance and close this one.
     */
     if (hPrevInstance)                         
          {
          hWndPrevApp = FindWindow("6502Class", NULL);
          if (IsWindow(hWndPrevApp))
               {
               BringWindowToTop(hWndPrevApp);
               return TRUE;
               }
          }
     if (! InitApplication(hInstance, lpCmdLine))
          return FALSE;

     /*
     ** Load main menu accelerators
     */
     if (! (hAccel = LoadAccelerators(hInstance, "Accelerators")))
          {
          TermApplication();
          return FALSE;
          }

     /*
     ** Perform initializations that apply to a specific instance
     */
     if (! InitInstance(hInstance, nCmdShow))
          {
          TermApplication();
          return FALSE;
          }

     /*
     ** load system equates
     */
     LabelLoad(hInstance, hMainWnd);

     /*
     ** if a file has been given in the command line, we handle it.
     */
     if (lpCmdLine && *lpCmdLine)
          {
          MainClean(FALSE);
          strcpy(szBinPath, lpCmdLine);
          if (p = strrchr(lpCmdLine, '.'))
               if ((! stricmp(p, ".ATR")) || (! stricmp(p, ".XFD")))
                    strcpy(szAtrPath, lpCmdLine);
          PostMessage(hMainWnd, WM_OPENCMDLINE, 0, 0L);
          }

     /*
     ** Acquire and dispatch messages until a WM_QUIT message is received.
     */
     while (GetMessage(&msg, NULL, NULL, NULL))
          {
          if (! TranslateAccelerator(hMainWnd, hAccel, &msg))
               {
               TranslateMessage(&msg);
               DispatchMessage(&msg);
               }
          }

     /*
     ** free resources
     */
     MainClean(FALSE);
     DisResetLabelLines(0);
     LabelTerm();
     LabelFreeUser();
     DumpTerm();
     TermApplication();
     return msg.wParam;
}
